0

I have an input component that gets url as input and resolve it (resolve=get somehow a preview for that url):

var resolver = require('resolver');
var UrlResolver = React.createClass({
    statics: {
        storeListeners: {
            onResolverStore: [ ResolverStore ]
        }
    },
    getInitialState: function() {
        return { value: '', resolves: [] };
    },
    onResolverStore: function(data) {
        var resolves = [].concat(this.state.resolves, data);
        this.setState({ resolves: resolves });
    },
    handleChange: function(event) {
        var value = event.target.value;
        this.setState({ value: value });
        this.executeAction(resolver, value);
    },
    render: function () {
        return (
            <input value={ this.state.value } onChange={ this.handleChange } />
            {
                this.state.resolves.map(function(data) {
                    return <ResolveView data={ data } />;
                });
            }
        );
    }
});

As you can see, UrlResolver waits for changes on ResolverStore. Such changes can be happen when there is a change on the input. My problem is when I have 10 UrlResolvers on my view. In this situation, any change on one input will change ResolverStore that will trigger 10 different events and so 10 different setStates that will cause 10 re-renders. All this whereas only one input should handle this change. This way 9 components will also add a resolve data that is not belong to them.

What is the solution for such need?

Naor
  • 23,465
  • 48
  • 152
  • 268

1 Answers1

0

You seem to be using a Flux implementation that pushes data from a store to a component, which takes all state that the store is wrapping and pushes it to the components state. That becomes a problem when different components are only interested in a subset of that data, or if the data needs to be queried first.

My take on Flux is that data should be pulled in by components from stores instead of the other way around. That way, you get total flexibility in your component for what data you're interested in, without loosing the unidirectional data flow.

An example would be something like:

var MyComponent = React.createClass({
  getInitialState() {
    return {
      items: []
    };
  },
  componentWillMount() {
    MyStore.on('change', this.loadItems);
    this.loadItems();
  },
  componentWillUnmount() {
    MyStore.off('change', this.loadItems);
  },
  loadItems() {
    MyStore.find(/* search params or find by id goes here */)
      .then(items => this.setState({items: items}));
  },
  render() {
    return (
      <ul>
        {this.state.items.map(item => <li>{item}</li>)}
      </ul>
      <button onClick={e => MyActions.add()}>Add new stuff</button>
    );
  }
});

As you see, it's up to the component to query the store for whatever data the component is interested in, but the component still subscribes to any changes to the state in the store. In this example it's an array of items, but it could just as well be a single item fetched by id passed to the component as a prop.

Pushing the entire state of a store works well if you have all state in memory, but for stores backed by a REST API or a WebSQL/IndexedDB database, I find this model to be simpler.

EDIT

To further clearify on why I perfer pull-from-store instead of push-to-component. By having the component only fetch the data it needs instead of having the store push it's entire state at you, it's easier to see if it's worth re-rendering.

In my example it does call loadItems in each component instance as soon as the action is called, but by having pull-from-store, you can let your component short-circuit that by checking if the data that the component is interested in has changed. This can be done either by having your stores work with immutable data, which means that you can use reference equality to determine if something has changed. Another option is to pass what was actually changed in the change event. The component can then check if the id of the changed item is something that it cares about, and skip the re-render if it's not.

I would advise against such checks though, because it's easy to fall into the trap of the component knowing to little to know if it should do that re-render or not. If you instead return immutable lists/records/maps from your stores, it's simply an equality check on what was returned with what it previously had to know if something the component was interested in has changed.

Anders Ekdahl
  • 22,685
  • 4
  • 70
  • 59
  • I don't get it. You have the same problem as I have. In case of multiple MyComponent components in your screen, if one component will trigger MyActions.add(), all your components will run loadItems(). In my example, the only component that wanted loadItems() to run is the first component that invoked MyActions.add(). – Naor Apr 17 '15 at 11:09
  • I've expanded a bit in an edit to the post, which I hope will clearify it a bit. – Anders Ekdahl Apr 17 '15 at 11:20
  • I want to avoid the other callbacks from being executed. I can think of many ways how to filter the result, but I don't want other callbacks to happen. It seems like I don't need to use flux action here. What do you think? – Naor Apr 17 '15 at 13:31
  • Another option is to have more than just a `change` event which Flux implementations usually only have. You can have whichever events that might suit your needs. You could make it possible to subscribe to `change` events and pass in an id as well, telling the store to only call the listener if the object with that id is changed. – Anders Ekdahl Apr 17 '15 at 13:44