Question

Facebook React encourages you to separate mutable (state) and immutable (props) state:

Try to keep as many of your components as possible stateless. By doing this you'll isolate the state to its most logical place and minimize redundancy, making it easier to reason about your application.

When the state changes, you are supposed to call setState to trigger virtual DOM diff, which will cause a real DOM update only when this is needed.

There is a way to trigger DOM update manually by calling forceUpdate but it is discouraged:

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your application much simpler and more efficient.

However, all React+Backbone examples I have seen ignore this advice and store models and collections in props and call forceUpdate:

Even React's own example uses forceUpdate:

Is there a better way, though, and what benefits would it give?

Was it helpful?

Solution

Pete's answer is great.

Backbone models are inherently mutative, which (while not a problem in itself) means that when rerendering, you won't have the old version of the model to compare to. This makes it harder to do intelligent optimizations by defining shouldComponentUpdate methods in key places on your components. (You also lose out on the ability to easily store old versions of your model for other reasons, like implementing undo.)

Calling forceUpdate merely skips shouldComponentUpdate and forces the component to rerender. Note that calling render is usually cheap, and React will still only touch the DOM if the output of render has changed, so performance problems here aren't common. However, if you have the choice to use immutable data (including passing around raw model property objects from toJSON() as Pete suggests), I'd highly recommend it.

OTHER TIPS

Until there is a better answer, let me quote Pete Hunt, a core React developer:

The big win with Backbone models was it managed your data flow for you. When you called set() it would inform your app that data changed. With React you'll find this to be less necessary because all you need to do is inform the component that owns the state via a callback and React ensures that all children are up-to-date. So this part of backbone is less useful IMO (and people tend to use backbone in this way with React anyway).

You don't have to pass pure JSON (though that's what I tend to do and it works well for simple data models), but you will see lots of advantages if you keep your objects immutable.

You can try this out by just calling toJSON() on your backbone models and seeing how you like it vs passing the models around.

(emphasis mine)

Interestingly, Backbone.React.Component is the only example I found that uses toJSON, but for some reason also uses setProps instead of setState (which is discouraged too).

Update

I made a simple mixin based on Pete Hunt's approach (no setProps, no forceUpdate):

define(function () {

  'use strict';

  var Backbone = require('backbone'),
      _ = require('underscore');

  var BackboneStateMixin = {
    getInitialState: function () {
      return this.getBackboneState(this.props);
    },

    componentDidMount: function () {
      if (!_.isFunction(this.getBackboneState)) {
        throw new Error('You must provide getBackboneState(props).');
      }

      this._bindBackboneEvents(this.props);
    },

    componentWillReceiveProps: function (newProps) {
      this._unbindBackboneEvents();
      this._bindBackboneEvents(newProps);
    },

    componentWillUnmount: function () {
      this._unbindBackboneEvents();
    },

    _updateBackboneState: function () {
      var state = this.getBackboneState(this.props);
      this.setState(state);
    },

    _bindBackboneEvents: function (props) {
      if (!_.isFunction(this.watchBackboneProps)) {
        return;
      }

      if (this._backboneListener) {
        throw new Error('Listener already exists.');
      }

      if (!props) {
        throw new Error('Passed props are empty');
      }

      var listener = _.extend({}, Backbone.Events),
          listenTo = _.partial(listener.listenTo.bind(listener), _, _, this._updateBackboneState);

      this.watchBackboneProps(props, listenTo);
      this._backboneListener = listener;
    },

    _unbindBackboneEvents: function () {
      if (!_.isFunction(this.watchBackboneProps)) {
        return;
      }

      if (!this._backboneListener) {
        throw new Error('Listener does not exist.');
      }

      this._backboneListener.stopListening();
      delete this._backboneListener;
    }
  };

  return BackboneStateMixin;

});

It doesn't care about what kind of models or collections you have.

The convention is that Backbone models go in props and their JSON is automatically put by mixin into state. You need to override getBackboneState(props) for this to work, and optionally watchBackboneProps to tell the mixin when to call setState with fresh values.

Usage example:

var InfoWidget = React.createClass({
  mixins: [BackboneStateMixin, PopoverMixin],

  propTypes: {
    stampModel: React.PropTypes.instanceOf(Stamp).isRequired
  },

  // Override getBackboneState to tell the mixin
  // HOW to transform Backbone props into JSON state

  getBackboneState: function (props) {
    var stampModel = props.stampModel,
        primaryZineModel = stampModel.getPrimaryZine();

    return {
      stamp: stampModel.toJSON(),
      toggleIsLiked: stampModel.toggleIsLiked.bind(stampModel),
      primaryZine: primaryZineModel && primaryZineModel.toJSON()
    };
  },

  // Optionally override watchBackboneProps to tell the mixin
  // WHEN to transform Backbone props into JSON state

  watchBackboneProps: function (props, listenTo) {
    listenTo(props.stampModel, 'change:unauth_like_count change:is_liked');
    listenTo(props.stampModel.get('zines'), 'all');
  },

  render: function () {
    // You can use vanilla JSON values of this.state.stamp,
    // this.state.toggleIsLiked and this.state.primaryZine
    // or whatever you return from getBackboneState
    // without worrying they may point to old values
  }
}

Note: mixin requires Underscore 1.6.0+.

I'm the developer behind Backbone.React.Component. The reason why we're using setProps is because this is only intended to be called by the component owner (greatest parent). The way I see it, props is better to use for reactive updates (and to pass to child components) than state, but if you can point me some reasons why state is better I'll be happy to start developing towards that change.

For instance sometimes I've got components that delegate to others, where transferPropsTo is pretty handy. Using state kind of makes it harder to achieve that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top