52

How do people typically approach having "global" data in a React application?

For example, say I have the following data for a user once they're logged into my app.

user: {
  email: 'test@user.com',
  name: 'John Doe'
}

This is data that almost any component in my app might like to know about - so it could either render in a logged in or logged out state, or perhaps display the users email address if logged in.

From my understanding, the React way of accessing this data in a child component is for a top level component to own the data, and pass it to child components using properties, for example:

<App>
  <Page1/>
  <Page2>
    <Widget1/>
    <Widget2 user={user}/>
  </Page2>
</App>

But this seems unwieldy to me, as that would mean I'd have to pass the data through each composite, just to get it to the child that needed it.

Is there a React way of managing this type of data?

Note: This example is very simplified - I like to wrap intents up as composites so implementation details of entire UI features can be drastically changed as I see fit.

EDIT: I'm aware that by default, calling setState on my top level component would cause all child components to be re-rendered, and that in each child component I can render using whatever data I like (e.g. global data, not just state or props). But how are people choosing to notify only certain child components that they should be rendered?

Community
  • 1
  • 1
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
  • 1
    Many people (including myself) uses Backbone to handle data in React and then trigger a re-render when the models has changed. Or if you want you can just use a global Object. React doesn’t handle data on it’s own, it just provides methods for re-rendering the App. – David Hellsing Sep 30 '14 at 18:01
  • 1
    thanks for the feedback! So are you simply just calling `setState` on your "main" component, and letting React re-render everything, regardless of whether or not it's state has changed? This works of course, but isn't render friendly (http://stackoverflow.com/a/24719289/26510) – Brad Parks Sep 30 '14 at 18:10
  • 1
    We use `this.forceUpdate()` on the top component inside the backbone handlers. I believe this is the appropriate method, but you’ll find more alternatives and info about the pros/cons here: http://stackoverflow.com/questions/21709905/can-i-avoid-forceupdate-when-using-react-with-backbone – David Hellsing Sep 30 '14 at 20:43
  • interesting... definitely a good read.... sounds like it still causes a render to happen, but if the render results in the same html, no real DOM changes are made... good to know! – Brad Parks Sep 30 '14 at 23:43

6 Answers6

20

Since I originally answered this question, it's become apparent to me that React itself doesn't support "global" data in any sense - it is truly meant to manage the UI and that's it. The data of your app needs to live somewhere else. Having said that, it does now support accessing global context data as detailed in this other answer on this page. Here's a good article by Kent Dodds on how the context api has evolved, and is now officially supported in React.

The context approach should only be used for truly global data. If your data falls into any other category, then you should do as follows:

  • Facebook themselves solve this problem using their own Flux library.
  • Mobx and Redux are similar to Flux, but seem to have more popular appeal. They do the same thing, but in a cleaner, more intuitive way.

I'm leaving my original edits to this answer below, for some history.

OLD ANSWER:


The best answer I've found for this so far are these 2 React mixins, which I haven't had a chance to try, but they sound like they'll address this problem:

https://github.com/dustingetz/react-cursor

and this similar library:

https://github.com/mquan/cortex

MAJOR NOTE: I think this is a job for Facebook's Flux, or something similar (which the above are). When the data flow gets too complex, another mechanism is required to communicate between components other than callbacks, and Flux and it's clones seem to be it....

Brad Parks
  • 66,836
  • 64
  • 257
  • 336
  • 2
    Good to know: the link to the 'good article by Kent Dodds' seems to require multiple layers of registration and sign-up, but it's possible to bypass all that by just scrolling down below the fold. – Michael Scheper Dec 20 '18 at 04:50
  • 2
    Good point - I just went and changed the link to a more updated article from Kent Dodds that goes straight to his site. – Brad Parks Dec 20 '18 at 12:35
13

Use the React Context Property This is specifically for passing global data sets down the chain without explicitly forwarding them. It does complicate your Component lifecycle functions though, and note the cautions offered on the page I've linked.

Steve Taylor
  • 412
  • 1
  • 4
  • 11
3

You can use the React Context API for passing global data down to deeply nested child components. Kent C. Dodds wrote an extensive article on it on Medium React’s ⚛️ new Context API. It'll help in getting a better understanding of how to use the API.

Roy Scheffers
  • 3,832
  • 11
  • 31
  • 36
mukeshmandiwal
  • 244
  • 2
  • 6
3

I think React.createContext() is perfect solution for your purpose. React will re-render only components, that listen context changes with useContext hook.
Here is a simple snippet for your code:

export const CurrentUser = React.createContext({})
 
const App = () =>
{
    const User = getUser() // any authorisation method 
    return <>
        <CurrentUser.Provider value={User}> 
            <App>
                <Page1/>
                <Page2>
                    <Widget1/>
                    <Widget2/>
                </Page2>
            </App>
        </CurrentUser.Provider>
    </>
}  

const Widget2 = () =>
{
    const User = useContext(CurrentUser)
    return <>{User?.name}</>
}

In case if you want to control re-renders directly, you can use React.memo in nested components. For example, if you need re-render component only after specific attribute change.

Also, with nesting context values, you can reach good flexibility of your app. You can pass different context values for different part of your application.

export const CurrentUser = React.createContext({})
 
const App = () =>
{
    const User = getUser() // any authorisation method 
    const AnotherUser = getAnotherUser() // any authorisation method 
 
    return <>
        <CurrentUser.Provider value={User}> 
            <App>
                <Page1/>
                <CurrentUser.Provider value={AnotherUser}>  
                  <Page2>
                      <Widget1/>
                      <Widget2/>
                  </Page2>
                </CurrentUser.Provider>
            </App>
        </CurrentUser.Provider>
    </>
}  

const Widget2 = () =>
{
    const User = useContext(CurrentUser)
    return <>{User?.name}</>
}

2

What's wrong with just passing data all the way down the component chain via rendering all children with {...restOfProps}?

render(){
  const {propIKnowAbout1, propIKnowAbout2, ...restOfProps} = this.props;
  return <ChildComponent foo={propIKnowAbout1} bar={propIKnowAbout2} {...restOfProps}/>
}
BoxerBucks
  • 3,124
  • 2
  • 21
  • 26
1

There is Reactn https://www.npmjs.com/package/reactn
You use this.global and this.setGlobal to get and set the global state same as you do with the local state.
To be able to do so you only need to
import React from 'reactn';

Juan Lanus
  • 2,293
  • 23
  • 18