{"id":13805150,"url":"https://github.com/redbooth/backbone-redux","last_synced_at":"2025-04-06T18:16:55.022Z","repository":{"id":136163864,"uuid":"41880929","full_name":"redbooth/backbone-redux","owner":"redbooth","description":"Easy way to keep your backbone collections and redux store in sync.","archived":false,"fork":false,"pushed_at":"2018-03-29T20:01:43.000Z","size":42,"stargazers_count":191,"open_issues_count":4,"forks_count":12,"subscribers_count":39,"default_branch":"master","last_synced_at":"2025-03-30T15:12:09.748Z","etag":null,"topics":["backbone","backbone-collections","backbone-redux","redux","rest","synchronization"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/redbooth.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2015-09-03T19:59:50.000Z","updated_at":"2023-10-18T17:12:32.000Z","dependencies_parsed_at":"2024-01-03T01:30:38.883Z","dependency_job_id":null,"html_url":"https://github.com/redbooth/backbone-redux","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbooth%2Fbackbone-redux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbooth%2Fbackbone-redux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbooth%2Fbackbone-redux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbooth%2Fbackbone-redux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/redbooth","download_url":"https://codeload.github.com/redbooth/backbone-redux/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247526768,"owners_count":20953143,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backbone","backbone-collections","backbone-redux","redux","rest","synchronization"],"created_at":"2024-08-04T01:00:58.046Z","updated_at":"2025-04-06T18:16:55.001Z","avatar_url":"https://github.com/redbooth.png","language":"JavaScript","funding_links":[],"categories":["Other Integrations","Marks"],"sub_categories":["Backbone","[Backbone - Give your JS App some Backbone with Models, Views, Collections, and Events](http://backbonejs.org/)"],"readme":"backbone-redux\n===============\n\nThe easy way to keep your backbone collections and redux store in sync.\n\n[![npm](https://img.shields.io/npm/v/backbone-redux.svg?style=flat-square)](https://www.npmjs.com/package/backbone-redux)\n[![npm](https://img.shields.io/npm/dm/backbone-redux.svg?style=flat-square)](https://www.npmjs.com/package/backbone-redux)\n[![Travis](https://img.shields.io/travis/redbooth/backbone-redux.svg?style=flat-square)](https://travis-ci.org/redbooth/backbone-redux)\n\n```\nnpm install backbone-redux --save\n```\n\nCreates reducers and listeners for your backbone collections and fires action\ncreators on every collection change.\n\n**Documentation is a work-in-progress**. Feedback is welcome and encouraged.\n\n* [Why?](#why)\n* [How to use](#how-to-use)\n  * [Auto way](#auto-way)\n  * [Manual artesanal way](#manual-artesanal-way)\n* [Documentation](#documentation)\n  * [Configuration options](#configuration)\n    * [collectionMap](#collection-map)\n    * [indexesMap](#indexes-map)\n    * [serializer](#serializer)\n  * [API reference](#api-reference)\n    * [syncCollections](#sync-collections)\n    * [buildReducers](#build-reducers)\n    * [buildEars](#build-ears)\n    * [actionFabric](#action-fabric)\n    * [reducerFabric](#reducer-fabric)\n* [Examples](#examples)\n\n\n### Why?\n\n* You can start migrating your apps from backbone to react+redux in no time.\n* No need to worry about migrated/legacy parts of your app being out of sync,\n  because both are using the single source of truth.\n* No boilerplate.\n* You can hide all new concepts like `reducers`, `stores`, `action creators`,\n  `actions` and `purity` from other developers in your team to avoid brain\n  overloading.\n* You have REST-adapter to your server out-of-the-box. Most React projects end\n  up implementing an ad hoc, bug-ridden implementation of Backbone.Collection\n  not only once, but for each store.\n* You have separation between server-data and UI-data. The later is flat, so\n  working with it is a pleasure in React.\n\n### How to use?\n#### Auto way\n\n\n```javascript\nimport { createStore, compose } from 'redux';\nimport { devTools } from 'redux-devtools';\nimport { syncCollections } from 'backbone-redux';\n\n//  Create your redux-store, include all middlewares you want.\nconst finalCreateStore = compose(devTools())(createStore);\nconst store = finalCreateStore(() =\u003e {}); // Store with an empty object as a reducer\n\n// Now just call auto-syncer from backbone-redux\n// Assuming you have Todos Backbone collection globally available\nsyncCollections({todos: Todos}, store);\n```\n\nWhat will happen?\n\n* `syncCollections` will create a reducer under the hood especially for your\n  collection.\n* `action creator` will be constructed with 4 possible actions: `add`, `merge`,\n  `remove`, and `reset`.\n* Special `ear` object will be set up to listen to all collection events and\n  trigger right actions depending on the event type.\n* Reducer will be registered in the store under `todos` key.\n* All previous reducers in your store will be replaced.\n\nYou are done. Now any change to `Todos` collection  will be reflected in the\nredux store.\n\nModels will be serialized before saving into the redux-tree: a result of\ncalling `toJSON` on the model + field called `__optimistic_id` which is equal\nto model's `cid`;\n\nResulting tree will look like this:\n\n```javascript\n{\n  todos: {\n    entities: [{id: 1, ...}, {id: 2, ...}],\n    by_id: {\n      1: {id: 1, ...},\n      2: {id: 2, ...}\n    }\n  }\n}\n```\n\n`entities` array is just an array of serialized models. `by_id` — default index\nwhich is created for you. It simplifies object retrieval, i.e.:\n`store.getState().todos.by_id[2]`\n\nSo, what is happening when you change `Todos`?\n\n```\nsomething (your legacy/new UI or anything really) changes Todos\n  -\u003e Todos collection emits an event\n    -\u003e ear catches it\n      -\u003e ActionCreator emits an action\n        -\u003e Reducer creates a new state based on this action\n          -\u003e New State is stored and listeners are notified\n            -\u003e React doing its magic\n```\n\n#### Manual Artesanal Way\n\nSometimes defaults that are provided by `syncCollections` are not enough.\n\nReasons could vary:\n* your collection could not be globally available\n* you need some custom rules when adding/removing/resetting collection\n* your collection have any dependency that should be processed too\n* etc\n\nIn all these cases you can't use `syncCollections`, but you can create your own\nears to mimic `syncCollections` behavior.\n\nAny `ear` should look something like this:\n\n```javascript\nimport { bindActionCreators } from 'redux';\n\nexport default function(collection, rawActions, dispatch) {\n  // binding action creators to the dispatch function\n  const actions = bindActionCreators(rawActions, dispatch);\n\n  actions.add(collection.models); // initial sync\n\n  // adding listeners\n  collection.on('add', actions.add);\n  collection.on('change', actions.merge);\n  collection.on('remove', actions.remove);\n  collection.on('reset', ({models}) =\u003e actions.reset(models));\n}\n```\n\nAs you can see, `ear` requires 3 attributes. `collection` and `dispatch`(this\nis just `store.dispatch`) you normally should already have, but how we can\ngenerate `rawActions`? You can use `actionFabric` that `backbone-redux`\nprovides:\n\n```javascript\nimport {actionFabric} from 'backbone-redux';\n\n// create some constants that will be used as action types\nconst constants = {\n  ADD: 'ADD_MY_MODEL',\n  REMOVE: 'REMOVE_MY_MODEL',\n  MERGE: 'MERGE_MY_MODEL',\n  RESET: 'RESET_MY_MODEL'\n};\n\n// you need some serializer to prepare models to be stored in the store.\n// This is the default one that is used in backbone-redux,\n// but you can create totally your own, just don't forget about __optimistic_id\nconst defaultSerializer = model =\u003e ({...model.toJSON(), __optimistic_id: model.cid});\n\nexport default actionFabric(constants, defaultSerializer);\n```\n\nDon't forget that `actionFabric` is just an object with a couple of methods,\nyou can extend it as you want.\n\nTime to generate a reducer:\n\n```javascript\nimport {reducerFabric} from 'backbone-redux';\n\n// the same constants, this is important\nconst constants = {\n  ADD: 'ADD_MY_MODEL',\n  REMOVE: 'REMOVE_MY_MODEL',\n  MERGE: 'MERGE_MY_MODEL',\n  RESET: 'RESET_MY_MODEL'\n};\n\n// any indexes that you want to be created for you\nconst index_map = {\n  fields: {\n    by_id: 'id'\n  },\n  relations: {\n    by_channel_id: 'channel_id'\n  }\n};\n\nexport default reducerFabric(constants, index_map);\n```\n\n\nAnd now we are ready to combine everything together:\n\n```javascript\nimport { syncCollections } from 'backbone-redux';\nimport store from './redux-store';\nimport customReducer from './reducer';\nimport customEar from './ear';\nimport customActions from './actions';\n\nexport default function() {\n  // start with syncing normal collections\n  const collectionsMap = {\n    collection_that_does_not_need_customization: someCollection\n  };\n\n  // we need to pass our prepared reducers into the store\n  // if you don't use syncCollections at all, you just need\n  // to create store normally with these reducers via\n  // combineReducers from redux\n  const extraReducers = {\n    custom_collection: customReducer\n  };\n\n  syncCollections(collectionsMap, store, extraReducers);\n\n  // now let's call the ear\n  customEar(customCollection, customActions, store.dispatch);\n}\n```\n\nDone, you have your custom ear placed and working.\n\n## Documentation\n\n### Configuration options\n\n#### collectionMap \u003ca id=\"collection-map\"\u003e\u003c/a\u003e\n\nA collection map is a plain object passed to `backbone-redux` functions to set\nup reducers for you.\n\nIf you don't need a custom serializer you can use:\n\n```javascript\n// keys are reducer names, and values are backbone collections\nconst collectionMap = {\n  reducer_name: collection\n}\n```\n\nIf you want, you can add change configuration by specifying `serializer` and `indexes_map` keys.\n\n```javascript\n// keys are reducer names, and values are objects defining collection and serializer\nconst collectionMap = {\n  reducer_name: {\n    collection: collection,\n    serializer: serializer,\n    indexes_map: indexes_map\n  }\n}\n```\n\n#### indexesMap \u003ca id=\"indexes-map\"\u003e\u003c/a\u003e\n\nWith `indexesMap` you can specify the way your entities are indexed in the tree.\n\n`fields` lets you access a *single* entity by a field (for example `id`, `email`, etc).\n\n`relation` groups entities by a field value (for example `parent_id`).\n\nExample:\n\nI have a `people` collection of models with 4 fields: `name`,\n`id`, `token`, and `org_id`. And I want to have indexes for all fields except\n`name`.\n\n```javascript\nconst jane = new Backbone.Model({id: 1, name: 'Jane', org_id: 1, token: '001'});\nconst mark = new Backbone.Model({id: 2, name: 'Mark', org_id: 2, token: '002'});\nconst sophy = new Backbone.Model({id: 3, name: 'Sophy', org_id: 1, token: '003'});\nconst people = new Backbone.Collection([jane, mark, sophy]);\n\nconst indexesMap = {\n  fields: {\n    by_id: 'id',\n    by_token: 'token'\n  },\n  relations: {\n    by_org_id: 'org_id'\n  }\n};\n\nsyncCollections({\n  people: {\n    collection: people,\n    indexes_map: indexesMap\n  }\n}, store);\n\n/**\n  store.getState().people =\u003e\n\n  {\n    entities: [\n      {id: 1, name: 'Jane', org_id: 1, token: '001', __optimistic_id: 'c01'},\n      {id: 2, name: 'Mark', org_id: 2, token: '002', __optimistic_id: 'c02'},\n      {id: 3, name: 'Sophy', org_id: 1, token: '003', __optimistic_id: 'c03'}\n    ],\n    by_id: {\n      1: {id: 1, name: 'Jane', org_id: 1, token: '001', __optimistic_id: 'c01'},\n      2: {id: 2, name: 'Mark', org_id: 2, token: '002', __optimistic_id: 'c02'},\n      3: {id: 3, name: 'Sophy', org_id: 1, token: '003', __optimistic_id: 'c03'}\n    },\n    by_token: {\n      '001': {id: 1, name: 'Jane', org_id: 1, token: '001', __optimistic_id: 'c01'},\n      '002': {id: 2, name: 'Mark', org_id: 2, token: '002', __optimistic_id: 'c02'},\n      '003': {id: 3, name: 'Sophy', org_id: 1, token: '003', __optimistic_id: 'c03'}\n    },\n    by_org_id: {\n      1: [\n        {id: 1, name: 'Jane', org_id: 1, token: '001', __optimistic_id: 'c01'},\n        {id: 3, name: 'Sophy', org_id: 1, token: '003', __optimistic_id: 'c03'}\n      ],\n      2: [\n        {id: 2, name: 'Mark', org_id: 2, token: '002', __optimistic_id: 'c02'}\n      ]\n    }\n  }\n  */\n```\n\nAnd to remove indexes at all, just pass an empty object as `indexes_map` for `syncCollections`.\n\n#### serializer \u003ca id=\"serializer\"\u003e\u003c/a\u003e\n\nBy default models are stored in the tree by calling `model.toJSON` and adding\nan extra `__optimistic_id` which is the `model.cid`. You can serialize extra stuff by defining your own serializer function\n\n\n##### Arguments\n\n`model` *(Backbone.Model)*: Model to be serialized.\n\n##### Returns\n\n`serialized_model` *(Object)*: Plain object serialization of the model.\n\n\n### API Reference\n\n#### syncCollections(collectionMap, store, [extraReducers]) \u003ca id=\"sync-collections\"\u003e\u003c/a\u003e\n\nBuilds reducers and setups listeners in collections that dispatch actions to\nthe store. **syncCollections** will replace existing reducers in your store, but\nyou can still provide more reducers using the optional **extraReducers**\nargument.\n\n##### Arguments\n`collectionMap` *(CollectionMap)*: See [collectionMap](#collection-map).\n\n`store` *(Store)*: A Redux store.\n\n[`extraReducers`] *(Object)*: Optionally specify additional reducers in an\nobject whose values are reducer functions.  These reducers will be merged and combined\ntogether with the ones defined in the collectionMap.\n\n---\n\n#### buildReducers(collectionsMap) \u003ca id=\"build-reducers\"\u003e\u003c/a\u003e\n\nCreates reducers based on a\n[collectionMap](#collection-map),\nbasically calling [reducerFabric](#reducer-fabric) on each defined reducer.\n\n##### Arguments\n`collectionMap` *(CollectionMap)*: See [collectionMap](#collection-map).\n\n##### Returns\n`reducers` *(Object)*: An object whose keys are the collection names defined in\nthe input collectionMap, and values are generated reducer functions.\n\n---\n\n#### buildEars(collectionsMap, store) \u003ca id=\"build-ears\"\u003e\u003c/a\u003e\n\nCreates the basic action creators using [actionFabric](#action-fabric), and binds them to the\nappropriate Backbone.Collection events.\n\nWhen a collection event happens, the equivalent action will be dispatched.\n\n##### Arguments\n\n`collectionMap` *(CollectionMap)*: See [collectionMap](#collection-map).\n\n`store` *(Store)*: A Redux store.\n\n##### Arguments\n`collectionMap` *(CollectionMap)*: See [collectionMap](#collection-map).\n\n---\n\n#### actionFabric(actionTypesMap, serializer) \u003ca id=\"action-fabric\"\u003e\u003c/a\u003e\n\nReturns an object of action creators functions. This functions can be hooked to\nBackbone collections events `add`, `remove`, `change`, and `reset`.\n\nThe actions returned by this functions contain an `entities` field with the\nserialized models.\n\n##### Arguments\n\n`actionTypesMap` *(Object)*: Object to map from Backbone collection event to\naction constant type. Keys must be `ADD`, `REMOVE`, `MERGE` ( for the change\nevents ) and `RESET`.\n\n`serializer` *(Function)*: Model serializer function.\n\n##### Returns\n\n`actionCreators` *(Object)*: Returns an object whose keys are `add`, `remove`,\n`merge` and `reset`, and values are action creator functions.\n\n---\n\n#### reducerFabric(actionTypesMap, [indexesMap]) \u003ca id=\"reducer-fabric\"\u003e\u003c/a\u003e\n\n`actionTypesMap` *(Object)*: Object to map from Backbone collection event to\naction constant type. Keys must be `ADD`, `REMOVE`, `MERGE` ( for the change\nevents ) and `RESET`.\n\n[`indexesMap`] *(Object)*: Optionally define indices passing an [indexesMap](#indexes-map).\n\n---\n\n\n### Examples\n\n* [TodoMVC](https://github.com/redbooth/backbone-redux/tree/master/examples/todomvc)\n\n### Licence\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredbooth%2Fbackbone-redux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredbooth%2Fbackbone-redux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredbooth%2Fbackbone-redux/lists"}