Search code examples
javascriptreactjsrefluxjsdatastore

Reflux connectFilter still sending updates to all listening components


I am using the Reflux.connectFilter mixin to have a bunch of Graph components listen to changes in my GraphStore. Using the filter, they should only re-render when an element in the GraphStore's graphs array matching their ID is changed (or added/removed). However, when I update the a single element of the array, say by setting a name variable, I am still seeing all the listening Graphs re-render. Am I doing something wrong?

GraphStore

var Reflux       = require('reflux');
var GraphActions = require('./graphActions').GraphActions;

var GraphStore = Reflux.createStore({
  listenables: [GraphActions],
  init: function() {
    this.graphs         = [];
    this.metricMetaData = {};
  },
  onAddGraph: function(graphId, name) { // Called only by the Dashboard component that is a parent to all Graphs
       this.graphs.push(
          {
             id:   graphId,
             name: ""
          }
       );

       this.updateGraphs();
  },
  onSetName: function(graphId, name) { // Called only by the Dashboard component that is a parent to all Graphs
     for(var i = 0, gl = this.graphs.length; i < gl; ++i) {
        if(this.graphs[i].id === graphId) {
           this.graphs[i].name = name;
           this.updateGraphs();
           return;
        }
     }
  },
  ...
  updateGraphs: function() {
    this.trigger(this.graphs); // This is the only place in the store where trigger is called
  },
  getInitialState: function() {
    return this.graphs;
  }
});

module.exports = {GraphStore: GraphStore};

Graph

/** @jsx React.DOM */
var React        = require('react');
var Reflux       = require('reflux');
var GraphActions = require('./graphActions').GraphActions;
var GraphStore   = require('./graphStore').GraphStore;

var Graph = React.createClass({
  mixins: [Reflux.connectFilter(GraphStore, "graph", function(graphs) {
    return graphs.filter(function(graph) {
      return graph.id === this.props.id;
    }.bind(this))[0];
  })],
  propTypes: {
    id: React.PropTypes.string.isRequired
  },
  ...
  render: function() {
    if(typeof this.state.graph === "undefined") {
       return (<div>The graph has not been created in the store yet</div>);
    } else {
       return (<div>Graph name: {this.state.graph.name}</div>);
    }
  }
};

module.exports = {Graph: Graph};

Dashboard

/** @jsx React.DOM */
var React        = require('react');
var Graph        = require('./graph').graph;
var GraphActions = require('./graphActions').GraphActions;
var UUID         = require('uuid');    

var Dashboard = React.createClass({
  propTypes: {
    numGraphs: React.PropTypes.int.isRequired
  },
  ...
  render: function() {
    var graphs = [];
    for(var i = 0; i < this.props.numGraphs; ++i) {
        var currId = UUID.v4();
        GraphActions.addGraph(currId, "");
        graphs.push(<Graph id={currId} />);
    }

    return (<div>{graphs}</div>);
  }
};

module.exports = {Dashboard: Dashboard};

Solution

  • I haven't used Reflux, but I think the issue here is that all your Graph instances are listening to the GraphStore, and as soon as that store sends an event, all component instances will receive that event. They will filter out the data they're not interested in, but Reflux will still call setState on all instances, triggering them to re-render. Reflux does not (to my knowledge) short-circuit re-renders if the result of the filter function is the same as it was before.

    To short-circuit it and avoid re-rendering when you get the same data back, you need to implement the shouldComponentUpdate method on the component and compare the new state to the old state, and return false if it's the same.

    A popular approach to this is to use [1] Immutable.js together with the [2] PureRenderMixin, which does the short-circuiting for you.

    [1] https://github.com/facebook/immutable-js

    [2] https://facebook.github.io/react/docs/pure-render-mixin.html