backbone-redux
The easy way to keep your backbone collections and redux store in sync.

npm install backbone-redux --save
Creates reducers and listeners for your backbone collections and fires action
creators on every collection change.
Documentation is a work-in-progress. Feedback is welcome and encouraged.
Why?
- You can start migrating your apps from backbone to react+redux in no time.
- No need to worry about migrated/legacy parts of your app being out of sync,
because both are using the single source of truth.
- No boilerplate.
- You can hide all new concepts like
reducers
, stores
, action creators
,
actions
and purity
from other developers in your team to avoid brain
overloading. - You have REST-adapter to your server out-of-the-box. Most React projects end
up implementing an ad hoc, bug-ridden implementation of Backbone.Collection
not only once, but for each store.
- You have separation between server-data and UI-data. The later is flat, so
working with it is a pleasure in React.
How to use?
Auto way
import { createStore, compose } from 'redux';
import { devTools } from 'redux-devtools';
import { syncCollections } from 'backbone-redux';
const finalCreateStore = compose(devTools())(createStore);
const store = finalCreateStore(() => {});
syncCollections({todos: Todos}, store);
What will happen?
syncCollections
will create a reducer under the hood especially for your
collection.action creator
will be constructed with 4 possible actions: add
, merge
,
remove
, and reset
.- Special
ear
object will be set up to listen to all collection events and
trigger right actions depending on the event type. - Reducer will be registered in the store under
todos
key. - All previous reducers in your store will be replaced.
You are done. Now any change to Todos
collection will be reflected in the
redux store.
Models will be serialized before saving into the redux-tree: a result of
calling toJSON
on the model + field called __optimistic_id
which is equal
to model's cid
;
Resulting tree will look like this:
{
todos: {
entities: [{id: 1, ...}, {id: 2, ...}],
by_id: {
1: {id: 1, ...},
2: {id: 2, ...}
}
}
}
entities
array is just an array of serialized models. by_id
— default index
which is created for you. It simplifies object retrieval, i.e.:
store.getState().todos.by_id[2]
So, what is happening when you change Todos
?
something (your legacy/new UI or anything really) changes Todos
-> Todos collection emits an event
-> ear catches it
-> ActionCreator emits an action
-> Reducer creates a new state based on this action
-> New State is stored and listeners are notified
-> React doing its magic
Manual Artesanal Way
Sometimes defaults that are provided by syncCollections
are not enough.
Reasons could vary:
- your collection could not be globally available
- you need some custom rules when adding/removing/resetting collection
- your collection have any dependency that should be processed too
- etc
In all these cases you can't use syncCollections
, but you can create your own
ears to mimic syncCollections
behavior.
Any ear
should look something like this:
import { bindActionCreators } from 'redux';
export default function(collection, rawActions, dispatch) {
const actions = bindActionCreators(rawActions, dispatch);
actions.add(collection.models);
collection.on('add', actions.add);
collection.on('change', actions.merge);
collection.on('remove', actions.remove);
collection.on('reset', ({models}) => actions.reset(models));
}
As you can see, ear
requires 3 attributes. collection
and dispatch
(this
is just store.dispatch
) you normally should already have, but how we can
generate rawActions
? You can use actionFabric
that backbone-redux
provides:
import {actionFabric} from 'backbone-redux';
const constants = {
ADD: 'ADD_MY_MODEL',
REMOVE: 'REMOVE_MY_MODEL',
MERGE: 'MERGE_MY_MODEL',
RESET: 'RESET_MY_MODEL'
};
const defaultSerializer = model => ({...model.toJSON(), __optimistic_id: model.cid});
export default actionFabric(constants, defaultSerializer);
Don't forget that actionFabric
is just an object with a couple of methods,
you can extend it as you want.
Time to generate a reducer:
import {reducerFabric} from 'backbone-redux';
const constants = {
ADD: 'ADD_MY_MODEL',
REMOVE: 'REMOVE_MY_MODEL',
MERGE: 'MERGE_MY_MODEL',
RESET: 'RESET_MY_MODEL'
};
const index_map = {
fields: {
by_id: 'id'
},
relations: {
by_channel_id: 'channel_id'
}
};
export default reducerFabric(constants, index_map);
And now we are ready to combine everything together:
import { syncCollections } from 'backbone-redux';
import store from './redux-store';
import customReducer from './reducer';
import customEar from './ear';
import customActions from './actions';
export default function() {
const collectionsMap = {
collection_that_does_not_need_customization: someCollection
};
const extraReducers = {
custom_collection: customReducer
};
syncCollections(collectionsMap, store, extraReducers);
customEar(customCollection, customActions, store.dispatch);
}
Done, you have your custom ear placed and working.
Documentation
Configuration options
collectionMap
A collection map is a plain object passed to backbone-redux
functions to set
up reducers for you.
If you don't need a custom serializer you can use:
const collectionMap = {
reducer_name: collection
}
If you want, you can add change configuration by specifying serializer
and indexes_map
keys.
const collectionMap = {
reducer_name: {
collection: collection,
serializer: serializer,
indexes_map: indexes_map
}
}
indexesMap
With indexesMap
you can specify the way your entities are indexed in the tree.
fields
lets you access a single entity by a field (for example id
, email
, etc).
relation
groups entities by a field value (for example parent_id
).
Example:
I have a people
collection of models with 4 fields: name
,
id
, token
, and org_id
. And I want to have indexes for all fields except
name
.
const jane = new Backbone.Model({id: 1, name: 'Jane', org_id: 1, token: '001'});
const mark = new Backbone.Model({id: 2, name: 'Mark', org_id: 2, token: '002'});
const sophy = new Backbone.Model({id: 3, name: 'Sophy', org_id: 1, token: '003'});
const people = new Backbone.Collection([jane, mark, sophy]);
const indexesMap = {
fields: {
by_id: 'id',
by_token: 'token'
},
relations: {
by_org_id: 'org_id'
}
};
syncCollections({
people: {
collection: people,
indexes_map: indexesMap
}
}, store);
And to remove indexes at all, just pass an empty object as indexes_map
for syncCollections
.
serializer
By default models are stored in the tree by calling model.toJSON
and adding
an extra __optimistic_id
which is the model.cid
. You can serialize extra stuff by defining your own serializer function
Arguments
model
(Backbone.Model): Model to be serialized.
Returns
serialized_model
(Object): Plain object serialization of the model.
API Reference
Builds reducers and setups listeners in collections that dispatch actions to
the store. syncCollections will replace existing reducers in your store, but
you can still provide more reducers using the optional extraReducers
argument.
Arguments
collectionMap
(CollectionMap): See collectionMap.
store
(Store): A Redux store.
[extraReducers
] (Object): Optionally specify additional reducers in an
object whose values are reducer functions. These reducers will be merged and combined
together with the ones defined in the collectionMap.
buildReducers(collectionsMap)
Creates reducers based on a
collectionMap,
basically calling reducerFabric on each defined reducer.
Arguments
collectionMap
(CollectionMap): See collectionMap.
Returns
reducers
(Object): An object whose keys are the collection names defined in
the input collectionMap, and values are generated reducer functions.
buildEars(collectionsMap, store)
Creates the basic action creators using actionFabric, and binds them to the
appropriate Backbone.Collection events.
When a collection event happens, the equivalent action will be dispatched.
Arguments
collectionMap
(CollectionMap): See collectionMap.
store
(Store): A Redux store.
Arguments
collectionMap
(CollectionMap): See collectionMap.
actionFabric(actionTypesMap, serializer)
Returns an object of action creators functions. This functions can be hooked to
Backbone collections events add
, remove
, change
, and reset
.
The actions returned by this functions contain an entities
field with the
serialized models.
Arguments
actionTypesMap
(Object): Object to map from Backbone collection event to
action constant type. Keys must be ADD
, REMOVE
, MERGE
( for the change
events ) and RESET
.
serializer
(Function): Model serializer function.
Returns
actionCreators
(Object): Returns an object whose keys are add
, remove
,
merge
and reset
, and values are action creator functions.
reducerFabric(actionTypesMap, [indexesMap])
actionTypesMap
(Object): Object to map from Backbone collection event to
action constant type. Keys must be ADD
, REMOVE
, MERGE
( for the change
events ) and RESET
.
[indexesMap
] (Object): Optionally define indices passing an indexesMap.
Examples
Licence
MIT