Search code examples
javascriptreactjsfluximmutable.js

React + Flux. Optimised proceedure for passing store state to component


I'm having a little conceptual difficulty with a certain aspect of the React/Flux architecture, I know, crazy, right. It has to do with how a Container should pass the Store to a Component, and how the Component should read from the Store, which as far as I see are interdependent.

As an example - I have a simple chart which updates the x and y range depending on changes to a form. I have a simple Store, updated from Dispatch events, "XRANGE_CHANGE" and "YRANGE_CHANGE", of an Action.

import Immutable from "immutable";
import { ReduceStore } from "flux/utils";
import Dispatcher from "../Dispatch";

class ChartStore extends ReduceStore {
    constructor() {
        super(Dispatcher);
    }
    getInitialState() {
        return Immutable.OrderedMap({
            xRange: [],
            yRange: []
        });
    }
    reduce(state, action) {
        switch(action.type) {
            case "XRANGE_CHANGE":
                return state.set("xRange", action.item);
            case "YRANGE_CHANGE":
                return state.set("yRange", action.item);
            default:
                console.error("Action type not found");
                return state;
        }
    }
}
export default new ChartStore();

And a Container, which will pass this Store to the Chart component;

import React from "react";
import { Container } from "flux/utils";
import ChartAction from "./ChartAction";
import ChartStore from "./ChartStore";
import Chart from "./Component";

class ExampleContainer extends React.Component {
    static calculateState() {
        const chartStore = ChartStore.getState(),
            xRange = chartStore.get("xRange"),
            yRange = chartStore.get("yRange")
        return {
            xRange: xRange,
            yRange: yRange,
            xChange: ChartAction.xChange,
            yChange: ChartAction.yChange
        };
    }
    static getStores() {
        return [ChartStore];
    }
    render() {
        const state = this.state;
        return <Chart
                // actions
                xChange={state.xChange}
                yChange={state.yChange}
                // !!!!! here's where my confusion lies !!!!!
                //store={state.chartStore}
                // ammended
                xRange={state.xRange}
                yRange={state.yRange}
            />
        </div>;
    }
}
export default Container.create(FinanceContainer);

The commented exclamation marks above indicate where I lose track of the "accepted" React way of doing things.I'm not quite sure of the best way to pass the Store to the Chart component, which will dictate how I read the Store within the component. I have a few options as far as I see, all work but could be completely wrong.

  • As above, I pass the entire store to the Component and in the Components' render function read store.get("xRange") or store.get("yRange")
  • In the Container I define xRange={chartStore.get("xRange")} etc.
  • In Either the Container or the Component I perform store.toJSON()/toObject() and read directly from the result.

I could be completely way off the mark with any of these scenarios. Or any of these ways could be fine.

Any advice would be appreciated. As I continue on I'd like to know I'm carrying out a sensible procedure. Thanks in advance.


Solution

  • As you've noticed, this isn't something with a definitive answer. But I think a good way of determining the "right" methodology is by looking at libraries that are written "for flux" and how they handle these problems. In particular, I would take a look at Redux (a flux implementation) and Reselect (an extension of Redux that addresses this issue further).

    The pattern that these libraries use is essentially that your calculateState method ought to transform the flux state into the relevant state information for that container. It should grab relevant information (e.g. state.get('xRange')) as well as possibly performing tranformations on the data held in state if helpful (e.g. range: {x: state.get('xRange'), y: state.get('yRange')}).

    As with most things in the flux pattern, the idea here is to provide a definitive "source of truth". You want every sub-component to interpret the flux state in the same way, and you want to have a single method to modify, should the data need to be computed differently. By doing an ETL of the flux state into the container, you achieve that. Should there be some future change to which piece of the flux state is needed for this section of your app, you would merely need to modify this calculateState method, as opposed to all lower usages of that data.