Skip to content

AndrewHenderson/marionette-redux

Repository files navigation

Marionette Redux

Marionette and Backbone bindings for Redux.

It's like React Redux, but for Marionette and Backbone.

Marionette Redux allows you to connect any Marionette or Backbone "component" to a Redux store.

Why Use This Library?

Migrating to React

If you've decided to migrate, Marionette Redux allows you to leverage Redux as a central data store to share app state between your React and Marionette components.

Examples:

Predictability

Marionette Redux introduces to a Marionette application a lifecycle that allows for deterministic DOM updates – consistent at first render and for any subsequent store updates (or component state changes) after the first render.

How Does It Work?

componentWillUpdate will execute when a display component (Marionette.View or Marionette.Behavior) first renders. This is where you put your DOM manipuation code.

A connected component's mapStateToProps will execute whenever the Redux store state changes. If the return object of display component's mapStateToProps differs from the last result, componentWillUpdate will execute.

Thus, you can set up components to always rely on the same set of values to determine their DOM state. This means that your views can execute the same callstack on first render and for any subsequent changes to the Redux store.

Installation

npm install --save marionette-redux

Usage

connect

Below is an example of a Marionette.View that has been "connected" to a Redux store. The following code could also be applied to a Marionette.Behavior.

var ConnectedView = MarionetteRedux.connect()(Marionette.View.extend({
  
  store: store,
  
  mapStateToProps: function(state) {
    return {
      isActive: state.isActive
    }
  },
  
  componentWillUpdate: function() {
    this.$el.toggleClass('active', this.props.isActive);
  }
}));

In this example, store is a property on the component, but connect will also look to window.store as a last resort. window.store can thus act similarly to React Redux's "Provider".

mixin

While connect is the recommended approach, Marionette Redux can also be used as a mixin.

Marionette.View.extend(MarionetteRedux.mixin);

Mappings

Mappings work the same as in React Redux. A change to the Redux store will result in this callback being executed on any "connected" components.

mapStateToProps

mapStateToProps can be a property on the component itself.

var ConnectedView = MarionetteRedux.connect()(Marionette.View.extend({
  mapStateToProps: function(state) {
    return {
      isActive: state.isActive
    }
  }
}));

Or it can be provided to connect as the first argument.

function mapStateToProps(state) {
  return {
    isActive: state.isActive
  }
}
var ConnectedView = MarionetteRedux.connect(mapStateToProps)(Marionette.View.extend({}));

mapDispatchToProps

mapDispatchToProps can also be a property on the component.

var ConnectedView = MarionetteRedux.connect()(Marionette.View.extend({
  mapDispatchToProps: function(dispatch) {
    return {
      dispatchMyEvent: function() {
        dispatch({
          type: 'MY_EVENT'
        });
      }
    }
  },
  events: {
    click: 'handleClick'
  },
  handleClick: function() {
    this.props.dispatchMyEvent();
  }
}));

Or it can provided to connect as the second argument.

var ConnectedView = MarionetteRedux.connect(null, mapDispatchToProps)(Marionette.View.extend({}));

Lifecycle

componentWillReceiveProps

This function is similar to React's componentWillReceiveProps. It provides an opportunity to execute any side effect functions before execution of componentWillUpdate.

Note: If the component is not a display component, meaining it is a Backbone.Model or Backbone.Collection, componentWillReceiveProps will still execute, however componentWillUpdate WILL NOT.

componentWillUpdate

This library ecourages the use of componentWillUpdate to ensure predictability of DOM state – one of the great things about React.

As demonstrated below, componentWillUpdate can be used to execute code that you want to run when a component is first rendered and after any subsequent changes to a component's props or state.

var ConnectedView = MarionetteRedux.connect()(Marionette.View.extend({
  
  store: store,
  
  mapStateToProps: function(state) {
    return {
      isActive: state.isActive
    }
  },
  
  componentWillUpdate: function() {
    this.$el.toggleClass('active', this.props.isActive);
  }
}));

State

If you prefer more granular control over store updates, we've provided state to components as well.

setState, getState, state, and getInitialState are all available for getting and setting state.

State works exactly the same as Marionette's modelEvents listeners, using the stateEvents object to define listeners:

var ConnectedView = MarionetteRedux.connect()(Marionette.View.extend({

  store: store,

  getInitialState: function() {
    return: {
      isActive: false
    }
  },
  stateEvents: {
    'change:isActive': 'onIsActiveChange'
  },
  onIsActiveChange: function(view, isActive) {
    this.$el.toggleClass('active', isActive);
  },
  mapStateToProps: function(state) {
    return {
      isActive: state.active === this.model.id
    }
  },
  componentWillReceiveProps: function(update) {
    this.setState({
      isActive: update.isActive
    });
  },
}));

As with changes to props, changes to a display component's state will execute componentWillUpdate.

Backbone

You also have the option to connect a Backbone.Model or Backbone.Collection.

Model

function mapStateToProps(state) {
  return {
    currency: state.currency
  }
}
var Model = Backbone.Model.extend({

  store: store,
  
  initialize: function() {
    // update the store on changes
    this.on('update', function() {
      store.dispatch({
        type: 'MODEL_UPDATE',
        data: this.toJSON()
      });
    })
  },
  
  componentWillReceiveProps: function(update) {
    this.set({
      currency: update.currency
    })
  }
});
var ConnectedModel = MarionetteRedux.connect(mapStateToProps)(Model);

Collection

var Collection = Backbone.Collection.extend({

  store: store,
  
  initialize: function() {
    // update the store on changes
    this.on('update', function() {
      store.dispatch({
        type: 'COLLECTION_UPDATE',
        data: this.toJSON()
      });
    })
  }
});
var ConnectedCollection = MarionetteRedux.connect()(Collection);

License

MIT