How to handle global state data into deeply nested components in Redux?
Asked Answered
E

2

12

So say you have a chat aplication with this component structure:

<ChatApp>
  <CurrentUserInfo>...</CurrentUserInfo>
  <ChatsPanel>...</ChatsPanel>
  <SelectedChatPanel>
    <MessagesList>
      <MessageBaloon>
        <MessageText></MessageText>
        <MessageUserHead></MessageUserHead>
      </MessageBaloon>
      ...
    </MessagesList>
  <SelectedChatPanel>
</ChatApp>

And a Redux state like:

{ 
  currentUser: ...,
  chatsList: ...,
  selectedChatIndex: ...,
  messagesList: [ ... ]
}

How would you make the current user information available to the <MessageUserHead> component (that will render the current user thumbnail for each message) without having to pass it along all the way from the root component thru all the intermediate components?

In the same way, how would you make information like current language, theme, etc available to every presentational/dumb component in the component tree without resorting to exposing the whole state object?

Epifaniaepifano answered 15/12, 2015 at 21:11 Comment(1)
github.com/vasanthk/react-bits/blob/master/patterns/… good article for this themeIvan
S
4

For information that is global to all of your "dumb" components, you could use react contexts.

A contrived example

// redux aware component
var ChatApp = React.createClass({
  childContextTypes: {
    language: React.PropTypes.string
  },
  getChildContext: function() {
    // or pull from your state tree
    return {language: "en"};
  },
  ...
}

// dumb components
var ExDumb = React.createClass({
  contextTypes: {
    language: React.PropTypes.string
  },

  render: function() {
    var lang = this.context.language;
    return ( <div /> );
   }
 });

In response to the comments, redux uses this context approach in their react-redux library.

And more abstractly for use outside of react, you could use some sort of pluck or selector function on the state tree, and return only a subset of the global state needed by dumb components.

Selmaselman answered 16/12, 2015 at 1:24 Comment(7)
Yes, I was aware of React's context, but I have 2 issues with that: first React's context API is experimental, they might remove it or change it significantly. Second, my question was about Redux's way of solving that, independently of the underlying implementation (React, AngularJS, etc).Epifaniaepifano
@Epifaniaepifano I tried to address a bit in my answer. But I would say, don't fear changes in context. It's such a simple API that it seems highly unlikely that it could change in any fundamental ways, and it's already become pretty core to basic functionality of React libraries, like react-redux. Redux itself doesn't provide any answer to your question, as it's not at all concerned with the concept of a "component hierarchy". You either use bindings like react-redux, or bring your own plumbing :)Suppository
The problem with this approach is that components can't be addressed using props anymoreAnyaanyah
@Anyaanyah Could you clarify? What do you mean by "addressed"?Suppository
yes sorry; i meant you cant do <ExDumb language="fr"/> anymore and your component now only works when context exists which breaks reusability ? Maybe some kind of HoC could help make the component reusable by passing context to props...Anyaanyah
We should not be using context in components using redux https://mcmap.net/q/101080/-react-with-redux-what-about-the-39-context-39-issueHeteropolar
@AbhinavSingi, avoid maybe, but when you are working with many nested components on large applications, the reduction of code that comes from using context is useful. That said, it's important to document the assumptions components have when using context.Selmaselman
S
12

(UPDATE: Having spent some time on option 4, I personally think it's the way to go. I published a library, react-redux-controller built around this approach.)

There are a few approaches that I know of from getting data from your root component, down to your leaf components, through the branches in the middle.

Props chain

The Redux docs, in the context of using react-redux, suggest passing the data down the whole chain of branches via props. I don't favor this idea, because it couples all the intermediate branch components to whatever today's app structure is. On the bright side, your React code would be fairly pure, and only coupled to Redux itself at the top level.

Selectors in all components

Alternatively, you could use connect to make data from your Redux store available, irrespective of where you are in the component tree. This decouples your components from one another, but it couples everything to Redux. I would note that the principle author of Redux is not necessarily opposed to this approach. And it's probably more performant, as it prevents re-renders of intermediary components due to changes in props they don't actually care about.

React children

I haven't thought a great deal about doing things this way, but you could describe your whole app structure at the highest level as nested components, passing in props directly to remote descendants, and using children to render injected components at the branch levels. However, taken to the extreme, this would make your container component really complicated, especially for intermediate components that have children of more than one type. Not sure if this is really viable at all for that reason.

React context

As first mentioned by @mattclemens, you can use the experimental context api to decouple your intermediate components. Yes, it's "experimental". Yes, the React team definitely doesn't seem to be in love with it. But keep in mind that this is exactly what Redux's connect uses to inject dispatch and props from selectors.

I think it strikes a nice balance. Components remain decoupled, because branch components don't need to care about the descendants' dependencies. If you only use connect at the root to set up the context, then all the descendents only need to couple to React's context API, rather than Redux. Components can freely be rearranged, as long as some ancestor is setting the required context properties. If the only component that sets context is the root component, this is trivially true.

The React team compares using context to global variables, but that feel like an exaggeration. It seems a lot more like dependency injection to me.

Suppository answered 16/12, 2015 at 19:54 Comment(2)
Update: React context has been documented in more detail - now that it has been adopted by various libraries. facebook.github.io/react/docs/context.htmlBaseline
@Baseline I don't think that page has changed in any significant way since I originally wrote my answer. There's been rumors that the context API might undergo a significant change, but I don't know if that's still true.Suppository
S
4

For information that is global to all of your "dumb" components, you could use react contexts.

A contrived example

// redux aware component
var ChatApp = React.createClass({
  childContextTypes: {
    language: React.PropTypes.string
  },
  getChildContext: function() {
    // or pull from your state tree
    return {language: "en"};
  },
  ...
}

// dumb components
var ExDumb = React.createClass({
  contextTypes: {
    language: React.PropTypes.string
  },

  render: function() {
    var lang = this.context.language;
    return ( <div /> );
   }
 });

In response to the comments, redux uses this context approach in their react-redux library.

And more abstractly for use outside of react, you could use some sort of pluck or selector function on the state tree, and return only a subset of the global state needed by dumb components.

Selmaselman answered 16/12, 2015 at 1:24 Comment(7)
Yes, I was aware of React's context, but I have 2 issues with that: first React's context API is experimental, they might remove it or change it significantly. Second, my question was about Redux's way of solving that, independently of the underlying implementation (React, AngularJS, etc).Epifaniaepifano
@Epifaniaepifano I tried to address a bit in my answer. But I would say, don't fear changes in context. It's such a simple API that it seems highly unlikely that it could change in any fundamental ways, and it's already become pretty core to basic functionality of React libraries, like react-redux. Redux itself doesn't provide any answer to your question, as it's not at all concerned with the concept of a "component hierarchy". You either use bindings like react-redux, or bring your own plumbing :)Suppository
The problem with this approach is that components can't be addressed using props anymoreAnyaanyah
@Anyaanyah Could you clarify? What do you mean by "addressed"?Suppository
yes sorry; i meant you cant do <ExDumb language="fr"/> anymore and your component now only works when context exists which breaks reusability ? Maybe some kind of HoC could help make the component reusable by passing context to props...Anyaanyah
We should not be using context in components using redux https://mcmap.net/q/101080/-react-with-redux-what-about-the-39-context-39-issueHeteropolar
@AbhinavSingi, avoid maybe, but when you are working with many nested components on large applications, the reduction of code that comes from using context is useful. That said, it's important to document the assumptions components have when using context.Selmaselman

© 2022 - 2024 — McMap. All rights reserved.