Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/redbooth/backbone-redux
Easy way to keep your backbone collections and redux store in sync.
https://github.com/redbooth/backbone-redux
backbone backbone-collections backbone-redux redux rest synchronization
Last synced: 24 days ago
JSON representation
Easy way to keep your backbone collections and redux store in sync.
- Host: GitHub
- URL: https://github.com/redbooth/backbone-redux
- Owner: redbooth
- License: mit
- Created: 2015-09-03T19:59:50.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2018-03-29T20:01:43.000Z (over 6 years ago)
- Last Synced: 2024-11-08T16:17:42.952Z (about 1 month ago)
- Topics: backbone, backbone-collections, backbone-redux, redux, rest, synchronization
- Language: JavaScript
- Homepage:
- Size: 41 KB
- Stars: 191
- Watchers: 40
- Forks: 12
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
- awesome-redux - backbone-redux - Easy way to keep your backbone collections and Redux store in sync. (Other Integrations / Backbone)
README
backbone-redux
===============The easy way to keep your backbone collections and redux store in sync.
[![npm](https://img.shields.io/npm/v/backbone-redux.svg?style=flat-square)](https://www.npmjs.com/package/backbone-redux)
[![npm](https://img.shields.io/npm/dm/backbone-redux.svg?style=flat-square)](https://www.npmjs.com/package/backbone-redux)
[![Travis](https://img.shields.io/travis/redbooth/backbone-redux.svg?style=flat-square)](https://travis-ci.org/redbooth/backbone-redux)```
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?](#why)
* [How to use](#how-to-use)
* [Auto way](#auto-way)
* [Manual artesanal way](#manual-artesanal-way)
* [Documentation](#documentation)
* [Configuration options](#configuration)
* [collectionMap](#collection-map)
* [indexesMap](#indexes-map)
* [serializer](#serializer)
* [API reference](#api-reference)
* [syncCollections](#sync-collections)
* [buildReducers](#build-reducers)
* [buildEars](#build-ears)
* [actionFabric](#action-fabric)
* [reducerFabric](#reducer-fabric)
* [Examples](#examples)### 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```javascript
import { createStore, compose } from 'redux';
import { devTools } from 'redux-devtools';
import { syncCollections } from 'backbone-redux';// Create your redux-store, include all middlewares you want.
const finalCreateStore = compose(devTools())(createStore);
const store = finalCreateStore(() => {}); // Store with an empty object as a reducer// Now just call auto-syncer from backbone-redux
// Assuming you have Todos Backbone collection globally available
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:
```javascript
{
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
* etcIn 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:
```javascript
import { bindActionCreators } from 'redux';export default function(collection, rawActions, dispatch) {
// binding action creators to the dispatch function
const actions = bindActionCreators(rawActions, dispatch);actions.add(collection.models); // initial sync
// adding listeners
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:```javascript
import {actionFabric} from 'backbone-redux';// create some constants that will be used as action types
const constants = {
ADD: 'ADD_MY_MODEL',
REMOVE: 'REMOVE_MY_MODEL',
MERGE: 'MERGE_MY_MODEL',
RESET: 'RESET_MY_MODEL'
};// you need some serializer to prepare models to be stored in the store.
// This is the default one that is used in backbone-redux,
// but you can create totally your own, just don't forget about __optimistic_id
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:
```javascript
import {reducerFabric} from 'backbone-redux';// the same constants, this is important
const constants = {
ADD: 'ADD_MY_MODEL',
REMOVE: 'REMOVE_MY_MODEL',
MERGE: 'MERGE_MY_MODEL',
RESET: 'RESET_MY_MODEL'
};// any indexes that you want to be created for you
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:
```javascript
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() {
// start with syncing normal collections
const collectionsMap = {
collection_that_does_not_need_customization: someCollection
};// we need to pass our prepared reducers into the store
// if you don't use syncCollections at all, you just need
// to create store normally with these reducers via
// combineReducers from redux
const extraReducers = {
custom_collection: customReducer
};syncCollections(collectionsMap, store, extraReducers);
// now let's call the ear
customEar(customCollection, customActions, store.dispatch);
}
```Done, you have your custom ear placed and working.
## Documentation
### Configuration options
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:
```javascript
// keys are reducer names, and values are backbone collections
const collectionMap = {
reducer_name: collection
}
```If you want, you can add change configuration by specifying `serializer` and `indexes_map` keys.
```javascript
// keys are reducer names, and values are objects defining collection and serializer
const collectionMap = {
reducer_name: {
collection: collection,
serializer: serializer,
indexes_map: indexes_map
}
}
```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`.```javascript
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);/**
store.getState().people =>{
entities: [
{id: 1, name: 'Jane', org_id: 1, token: '001', __optimistic_id: 'c01'},
{id: 2, name: 'Mark', org_id: 2, token: '002', __optimistic_id: 'c02'},
{id: 3, name: 'Sophy', org_id: 1, token: '003', __optimistic_id: 'c03'}
],
by_id: {
1: {id: 1, name: 'Jane', org_id: 1, token: '001', __optimistic_id: 'c01'},
2: {id: 2, name: 'Mark', org_id: 2, token: '002', __optimistic_id: 'c02'},
3: {id: 3, name: 'Sophy', org_id: 1, token: '003', __optimistic_id: 'c03'}
},
by_token: {
'001': {id: 1, name: 'Jane', org_id: 1, token: '001', __optimistic_id: 'c01'},
'002': {id: 2, name: 'Mark', org_id: 2, token: '002', __optimistic_id: 'c02'},
'003': {id: 3, name: 'Sophy', org_id: 1, token: '003', __optimistic_id: 'c03'}
},
by_org_id: {
1: [
{id: 1, name: 'Jane', org_id: 1, token: '001', __optimistic_id: 'c01'},
{id: 3, name: 'Sophy', org_id: 1, token: '003', __optimistic_id: 'c03'}
],
2: [
{id: 2, name: 'Mark', org_id: 2, token: '002', __optimistic_id: 'c02'}
]
}
}
*/
```And to remove indexes at all, just pass an empty object as `indexes_map` for `syncCollections`.
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
#### syncCollections(collectionMap, store, [extraReducers])
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](#collection-map).`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](#collection-map),
basically calling [reducerFabric](#reducer-fabric) on each defined reducer.##### Arguments
`collectionMap` *(CollectionMap)*: See [collectionMap](#collection-map).##### 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](#action-fabric), 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](#collection-map).
`store` *(Store)*: A Redux store.
##### Arguments
`collectionMap` *(CollectionMap)*: See [collectionMap](#collection-map).---
#### 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](#indexes-map).
---
### Examples
* [TodoMVC](https://github.com/redbooth/backbone-redux/tree/master/examples/todomvc)
### Licence
MIT