• Stars
    star
    191
  • Rank 202,877 (Top 4 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 9 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Easy way to keep your backbone collections and redux store in sync.

backbone-redux

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

npm npm Travis

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';

//  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:

{
  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) {
  // 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:

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:

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:

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

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:

// 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.

// keys are reducer names, and values are objects defining collection and serializer
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);

/**
  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.

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

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.

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

More Repositories

1

teambox

This is the legacy version of Teambox - the award-winning collaboration solution, inspired by Basecamp, Yammer and Twitter.
Ruby
1,864
star
2

react-native-auto-updater

A library to manage dynamic updates to React Native apps. Available as an NPM Package for iOS and Android.
Objective-C
1,509
star
3

gockerize

Package golang service into minimal docker containers.
Shell
666
star
4

free-file-icons

Platform-agnostic icons for audio, image, programming and office files.
611
star
5

scrum-poker-cards

588
star
6

viper-module-generator

Gem to generate VIPER modules to use them in your Objective-C/Swift projects.
Ruby
162
star
7

ssmp

Stupid-Simple Messaging Protocol.
122
star
8

lipwig

Golang implementation of the Stupid-Simple Messaging Protocol.
Go
98
star
9

departure

Percona's pt-online-schema-change runner for ActiveRecord migrations.
Ruby
86
star
10

SimplePagedView

A UIPageViewController replacement built to be as simple as possible
Swift
75
star
11

immortal

Make any ActiveRecord model paranoid by just including Immortal, and instead of being deleted from the database, the object will just marked as 'deleted' with a boolean field in the database.
Ruby
60
star
12

openjdk-trim

Tool to trim unneeded classes from an OpenJDK build.
Shell
47
star
13

react-native-quick-look

React Native Component for iOS QuickLook Framework.
Objective-C
37
star
14

aerofs-docker

Ready-to-use cloud-config file for the AeroFS appliance.
Shell
34
star
15

jssmp

Java implementation of the Stupid-Simple Messaging Protocol
Java
25
star
16

planning-time-android

Redbooth's planning poker app for Android.
Java
21
star
17

jnotify

Java bindings for native notification libraries.
C++
20
star
18

ssmperf

Load testing tool for SSMP servers.
Go
18
star
19

nps-surveys

Rails engine to add NPS surveys to your application.
Ruby
17
star
20

teambox-ruby-client

Ruby gem to access Teambox API
Ruby
16
star
21

npm-shrinkwrap-check

Util to check that package.json and npm-shrinkwrap.json are in sync.
JavaScript
14
star
22

fastqrcode

Fast and robust Python bindings for libqrencode.
C
11
star
23

redbooth-ruby

This is a Ruby wrapper for Redbooth's API.
Ruby
10
star
24

gerrit-slack-hooks

Make noise in Slack for Gerrit actions.
Shell
9
star
25

classy-immutable

Immutable instances of ES6 classes.
JavaScript
9
star
26

zendesk-elasticsearch

Index Zendesk tickets in Elasticsearch for easy searching.
Ruby
9
star
27

growl-java-bindings

Objective-C
8
star
28

rubocop-migrations

Ruby
5
star
29

aerofs-sdk-python

An AeroFS Private Cloud API SDK written in Python.
Python
4
star
30

SimpleNotificationBar

Swift
4
star
31

https-pushstate-server

A simple static file server that works with HTML5 Pushstate and HTTPS.
JavaScript
3
star
32

zendesk-help-center-backer

Back your Zendesk Help Center in git. Modify articles locally, and deploy directly to Zendesk.
Python
3
star
33

valkyrie

Golang implementation of the Zephyr relay protocol.
Go
3
star
34

aerofs-sdk-csharp

An AeroFS Private Cloud API SDK written in C#.
C#
2
star
35

retrace-server

Fast and simple TCP Server around Proguard's Retrace tool.
Java
2
star
36

fake-consul

Fakes a consul server. Usecase is for feature specs using Diplomat client
Ruby
2
star
37

api-docs

API v3 Documentation (depreciated).
CSS
2
star
38

aerofs-sdk-golang

An AeroFS Private Cloud API SDK written in Golang.
Go
1
star
39

aeroup

Secure AeroFS upload service. Send link, receive file.
JavaScript
1
star
40

switchboard

A service to perform real-time transcription and translation of audio streams.
Go
1
star
41

rbo-auth2-manager

AFOAuth2Manager in a Framework
Objective-C
1
star
42

freshdesk-help-center-backer

Back your Freshdesk Help Center in git. Modify articles locally, and deploy directly to Freshdesk.
Python
1
star