简体   繁体   中英

React: Changing Context in Consumer

The below question relates to the following sections in the React Context documentation:

  1. Dynamic Context
  2. Updating Context from a Nested Component

Disclaimer: Apologies for all the background information below. It provides context and will hopefully be helpful to future visitors .


What We Know

  • Link 1
    • The (default) context value is set to themes.dark (an object that contains two properties: foreground and background )
    • The default value is only ever used if there are no Providers above the Consumer in the component tree
    • In this case, there is a Provider present in the top-level component ( App )
    • This Provider ( App ), passes down its own state as the context value
    • It is smart to keep the values provided by a Provider equal in structure and type to the default context value (avoids Consumers getting confused)
    • Thus, state in the top-level component ( App ) holds an object of the same format as the default context value : themes.light
    • Conclusion from the above: When a Consumer reads the context , it reads App 's state
    • In other words, we are here using context to pass a parent ( App ) state deep down in the component tree, without having to pass it through every component in the middle
    • When state in the top-level component ( App ) changes, it re-renders and a new value for state is provided to the Consumer
    • This way, the Consumer reads the parent's state , via context
    • ...
    • Moving on, we see in link 1 that a function to set state ( toggleTheme ) is passed down the component tree as a normal prop
    • Thus, in link 1, context only contains an object that reads state
    • We are able to set state in the Consumer by passing the setState function as a normal prop from the Provider 's child, down through all the intermediate components, and in to the Consumer
    • Setting the state in the top-level component ( App ), leads to a re-render of itself, which leads to a re-render of the Provider , which then passes the new App state value down to its Consumer via context
    • As such, the Consumer always knows App 's state, via context
    • In conclusion, the flow is:
      1. Parent's state is provided as context value to child Consumer (s)
      2. Parent's state is updated by some child
      3. Parent re-renders
      4. Provider sees that context value ( App 's state ) has changed, and re-renders all its Consumer s with the new value
  • Link 2
    • In link 2, we set state in the Consumer , by passing the setState function within the context
    • This differs from link 1, where we relied on a normal prop to set state

Questions

We know from the docs that:

Every Context object comes with a Provider React component that allows consuming components to subscribe to context changes....

All consumers that are descendants of a Provider will re-render whenever the Provider's value prop changes.

  1. Let's assume we use a normal variable in App as the context value . We know from the above quote that changing it leads to the Provider re-rendering. Why then, do we bother using state as the context value? What is the benefit of that, vs. just using any normal variable in App ?
  2. Both the two approaches above allow us to update state . Why is link 2 incorporating the function to update state within state itself? Could we not just have it as a separate setState function, which is passed to the Consumer via context in an object that has two properties (one is state and the other is the standalone function to update state )?

Let's assume we use a normal variable in App as the context value. We know from the above quote that changing it leads to the Provider re-rendering. Why then, do we bother using state as the context value? What is the benefit of that, vs. just using any normal variable in App?

It's true that when the provider is rerendered with a changed value, any descendents that care about the context will rerender. But you need something to cause the provider to rerender in the first place. This will happen when App's state or its props change (or when you call forceUpdate, but don't do that). Presumably, this is at the top of your application, so there are no props coming in, which means you'll use state to cause it to rerender.

Both the two approaches above allow us to update state. Why is link 2 incorporating the function to update state within state itself? Could we not just have it as a separate setState function, which is passed to the Consumer via context in an object that has two properties (one is state and the other is the standalone function to update state)?

When deciding whether to rerender descendants due to a change of context, react will do basically a === between the old value and the new value. This is super quick and works well with React's preference for immutable data, but when using objects as your value you need to be careful that you're not making new objects on every render. For example, if App is doing something like the following, it will be creating a brand new object every time it renders, and thus will be forcing all the context consumers to rerender as well:

class App extends Component {
  state = {
    data: { 
      hello: 'world',
    }
  }

  updateData() {
    // some function for updating the state  
  }

  render() {
    return (
      <MyContext.Provider value={{ 
        data: this.state.data, 
        updateData: this.updateData
      }} />
    )
  }
}

So the example where they store the function in state is to make sure that the entire value they're providing does not change from one render to another.

Let's assume we use a normal variable in App as the context value. We know from the above quote that changing it leads to the Provider re-rendering. Why then, do we bother using state as the context value? What is the benefit of that, vs. just using any normal variable in App?

When you use state and update it - it does not matter at all if you are using provider or not - all the provider and components under it will update. Thats under official React Context documentation and is wrong. It means changing provider values DOES NOT call consumer updates at all.

You can validate this by making a separate component with state (which would not be inside provider) and assign that state variable to the provider. So when component state changes, value in state changes and in turn provider should notice this and update consumer. Which it is NOT doing.

In order to update components under consumers, you, unfortunately, have to do it manually. Unless your intension is to update everything under the provider.

This is true as of 2021-04-21 under React 17.0.2 - provider value changes are not being monitored and consumers are not being updated sadly. Unless you put all your provider in component with state, but changing its state forces updating all components under the provider. Sadly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM