Passing store state as props, or each component accessing global stores?
Asked Answered
P

2

26

I am a bit confused by the statements: "Renders the whole application" and "Passing state to child components".

Example 1:

I have a todos app with a AppComponent and TodosListComponent. The AppComponent grabs the array of todos from the store and passes it as a property to the TodosListComponent.

Example 2:

I have a huge application with lots state. I have like 50 components building up my app. Do I want to pass all the state from the stores from AppComponent down through all the 50 components?

So I am wondering, what is the convention? It makes more sense to me to let individual components listen directly to the stores they care about. The advantage is that only individual components rerender, but why then the concept of "the whole application rerender on state change"?

What are the pros and cons of each? What is the common convention?

Phosphoric answered 25/10, 2014 at 15:18 Comment(6)
Because when you have a bug and you go to look into it, all of your state is in one place, making everything super easy to track down and fix.Personable
So you would let one top component grab all the state from the stores and pass it through properties down to all 50 other nested components?Phosphoric
Not necessarily. It's highly dependent on what those 50 other nested components are. Use your best judgement. You can read this to start: facebook.github.io/react/docs/…Personable
Hm, yeah, though it is not the problem of putting state in stores or components. That is very clear I think. It is that I do not want to shove 100 states down through a top component and on each nested level of components redirect states down to lower levels of components. To me that feels very messy. But that is the only conclusion I can take from "rerender the whole application". It feels alot better to let components grab the state they want directly from stores... hm hm hmPhosphoric
Clearly highly opinion-based, since it literally asks for opinions.Fledgy
Puppy, yes but there was a clear objective question hidden under the opinionated wording :-)Fr
H
36

There are a few ways you can handle this. I think they're all valid and have their own trade-offs.

Get all the state and pass pieces of it to children

This is the technique you specifically asked about. Using this method, you'll have some function or method available to your top-level component that turns all the data from the stores into a "big bag of state" and then you'll selectively pass pieces of this data to child components. If those components have their own children, they'll pass it along as necessary.

The upside to this method is that it makes things generally easy to debug. If you have to change the way a piece of state is retrieved from a store, you only have to change it in the top-level component—as long as it gets passed down with the same name, the other components will "just work." If some piece of data is wrong, you should only need to look in one place to figure out why.

The downside to this technique what I call "props explosion"—you can end up passing a lot of properties around. I use this method in a medium-sized flux application, and a snippet of the top-level application component looks like this:

<section id="col-left">
  <Filters loading={this.state.loading}
            events={this.state.events}
            playbackRate={this.state.videoPlayback.playbackRate}
            autoPlayAudio={this.state.audioPlayback.autoPlay}
            role={this.state.role} />
</section>

<section id="col-center" className={leftPaneActive ? "" : "inactive"}>
  <SessionVideo videoUuid={this.state.session.recording_uuid}
                lowQualityVideo={this.state.session.low_quality_video_exists}
                playbackRate={this.state.videoPlayback.playbackRate} />
  <section id="transcript">
    <Transcript loading={this.state.loading}
                events={this.state.events}
                currentEvents={this.state.currentEvents}
                selection={this.state.selection}
                users={this.state.session.enrolled_users}
                confirmedHcs={this.state.ui.confirmedHcs}

                currentTime={this.state.videoPlayback.position}
                playing={this.state.videoPlayback.playing} />
  </section>
</section>

In particular, there can be a lot of components between the top-level one and some eventual child that do nothing with the data except pass it along, more closely coupling those components to their position in the hierarchy.

Overall, I like the debuggability this technique provides, though as the application grew larger and more complex I found it was not idea to do this with only a single top-level component.

Get all the state and pass it as one object

One of the developers at Facebook mentioned this technique. Here, you'll get a big bag of state, just as above, but you'll pass the whole thing (or entire sub-sections of it) rather than individual properties. By utilizing React.PropTypes.shape in child components, you can ensure that the right properties are getting passed.

The upside is you pass way fewer properties around; the above example might look more like this:

<section id="col-left">
  <Filters state={this.state} />
</section>

<section id="col-center" className={leftPaneActive ? "" : "inactive"}>
  <SessionVideo session={this.state.session}
                playback={this.state.videoPlayback} />
  <section id="transcript">
    <Transcript state={this.state} />
  </section>
</section>

The downside is that it becomes a little more difficult to deal with changes in the shape of the state; rather than just changing the top-level component, you'll have to track down everywhere that piece of data is used and change the way that component access the property. Also, shouldComponentUpdate can potentially become a little trickier to implement.

Allow components to get their own state

On the other end of the spectrum, you can grant application-specific (that is, non-reusable) child components to access the stores and build up their own state based on the store change events. Components that build their own state like this are sometimes called "controller-views" or, more commonly these days, "container components."

The upside, of course, is that you don't have to deal with passing properties around at all (other than change handlers and properties for more reusable components).

The downside, though, is that your components are more highly coupled to the stores—changing the stores or the data they provide (or the interface via which they provide that data) may force you to revisit the code for a larger number of components.

Also, as mentioned in the comments, this can potentially make server rendering a bit more difficult. If you only use properties (especially at only the top level), you can transport them more easily to the client and re-initialize React with the same properties. By allowing the stores to determine their own data, you need to somehow inject that data into the stores to allow the components to get that data.

A common approach, and one that I typically use now, is to make every component in your application only rely on props for global application state, and then decide if it makes more sense to (1) connect them directly to flux by wrapping them in a container, or (2) allow the props to be passed from some parent container.


There are abstractions that you might be able to use to make some of these techniques more viable. For example, a Facebook dev had this to say in a comment on Hacker News:

Now all your data is in stores, but how do you get it into the specific component that needs it? We started with large top level components which pull all the data needed for their children, and pass it down through props. This leads to a lot of cruft and irrelevant code in the intermediate components. What we settled on, for the most part, is components declaring and fetching the data they need themselves, except for some small, more generic components. Since most of our data is fetched asynchronously and cached, we've created mixins that make it easy to declare which data your component needs, and hook the fetching and listening for updates into the lifecycle methods (componentWillMount, etc).

Horrified answered 25/10, 2014 at 17:52 Comment(2)
Thanks for a great answer Brandon. This points out my confusion perfectly! I think it is also worth mentioning server side rendering here. If you pass state from "top component" it is a lot easier to use server side rendering as you pass the state from "one place". The last solution here would require you to probably override stores or make them behave differently on the server.Phosphoric
Thanks for the good explanations. Thinking of going 'hybrid', i.e. have the top-level component receive initial state via props, it will create and hold on to references to the stores, and the stores will be passed down to components via props, in a sort of DI/Repository style. Top-level component subscribes to store changes, calls forceUpdate.Pickaback
M
4

Jason Bonta from Facebook explained the concept of "Containers" in his React.js Conf 2015 talk.

To summarize: containers are simply components that wrap other components, and take care of any data-related concerns such as talking to stores, while the underlying component is focused solely on the view (markup/styles/etc.) and doesn't care where the data comes from.

This makes the component

  • highly re-usable because it can be wrapped with a different container when data needs to come from a different place,

  • not contain irrelevant state, therefore easier to implement and optimize shouldComponentUpdate, and

  • using composition rather than mixins for this aligns with what is likely the future of React with ES6, which does not have idiomatic mixins.

UPDATE March 2019: Look into React Hooks. With hooks, you can accomplish the same goal as described above, but abstract data-related concerns, such as talking to stores, in re-usable chunks of code that can be applied to multiple components. The ReactConf talk React Today and Tomorrow and 90% Cleaner React With Hooks by Dan Abramov does a great job of explaining hooks, and how they are different from both mixins and past composition approaches.

Md answered 15/7, 2015 at 15:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.