Calculating data dependent on non-committed (state) data of child/distant components in React

TL;DR edit in retrospect years later: there’s no solution that’s not gross as long as it’s just state data – you’ll need to also get it into a separate store somewhere somehow and can do whatever you want at that point. But read the question and the answer and the back-and-forth if you want some more background.


I have a table of two sections, each with various input values. Let’s say that it is a survey. Feeding data into this is straightforward; I have the typical model:

{ "sections": [ { "name": "a", values: { "A": 1, "B": 2, "C": 1, ... } }, ... ], ... }

And a component hierarchy like:

<Survey>
  <Section> (for each section)
    <ValueRow> (for each value)

I put the model into a prop on the survey and the right information is trickled down into the subcomponents. Each ValueRow has a text field and its ephemeral value reflected back into its own state. This works fine “on the way down”, in the one way flow that React is built for.

However, I also wish to show progress on the Section level and for the entire Survey, both simple things like number of fields filled out and statistical data needing the entire data set – what’s the average across sections, how many “1” answers do I have in total, what’s my grade (calculated from all the answers) and so on. Essentially, I’d also want to have:

<Survey>
  <SurveyWideStats>
  <Section> (for each section)
    <SectionWideStats>
    <ValueRow> (for each value)

This turns into a reduction of the current state instead of the model data. What’s the best way of doing this in React? Flux and Actions and Stores all seem to deal with how to handle the data once it has been committed to the model. What I want to do is to pluck all the state data and do something with it, but it also seems terribly gross for the SurveyWideStats element, for example, to go poking through the garbagestate of its sibling element’s children.

My current solution is to pass around an accumulation object and provide enough state to each component that it can keep calling that whenever something changes. This seems clear and divided enough, but it means that I have to have two passes and have to be careful not to start fiddling with state during rendering (at least since that’s when I call the accumulation object – I suppose there may be a better point during the lifecycle where I could call that). And in addition, it seems like this would be an obstacle to “pick up from” server side rendering.

What’s the best way? Is there an established pattern for this – preferably one where these things don’t have to be so custom and really tailored to the data all the time?

Answer

Two ways to do this:

  1. Pass the entire table as a prop to the highest component .
    Inside survey’s render function, calculate the stats, then pass them to the component as props, followed by the foreach loops over the table for the other children components. That way, your stats component is a pure component, does not need state and does not need to poke in siblings.

  2. Create a stats function in a store, and have the component call this to get the stats. NB best not to save the stats in a store, since it is clearly derived data. Unless for performance reasons.

Hope this helps!

UPDATE:
To handle changes by the user when they change an input value, you have two options, depending on your preference:

(Option 1 describes a pure component).

  1. (When you use flux pattern): Put the value of the input control in props. And whenever the user makes a change, fire an action to update a store, and have the store pass down updated props. So the (top) component notices a change event and rerenders. This creates more or less ‘live’ updates, e.g. when a user types a single character in an input field, the page title is updated immediately. The component with the input control does not have (and does not need) setState. This setup may become slow in really large component trees (because with each character, the entire tree is rerendered). But react is superfast and smartly only renders changes in de tree.

  2. Put the initial prop value in state (in getInitialState() and put the input value in state also. Typical example: user types a character in an input field, the change triggers a setState() and the component is rendered again. Only when the user clicks some save or commit button, an action is fired to save the value in a store.

UPDATE:
As a bonus, below the flow for updating stores and components. Flux Flow for updates and stats