How to update React Context from inside a child component?
Asked Answered
C

8

313

I have the language settings in the context as like below

class LanguageProvider extends Component {
  static childContextTypes = {
    langConfig: PropTypes.object,
  };

  getChildContext() {
    return { langConfig: 'en' };
  }

  render() {
    return this.props.children;
  }
}

export default LanguageProvider;

My application code will be something like below

<LanguageProvider>
  <App>
    <MyPage />
  </App>
</LanguageProvider>

My Page is having a component to switch the language

<MyPage>
  <LanguageSwitcher/>
</MyPage>

LanguageSwitcher in this MyPage need to update the context to change the language into 'jp' as below

class LanguageSwitcher extends Component {
  static contextTypes = {
    langConfig: PropTypes.object,
  };

  updateLanguage() {
    //Here I need to update the langConfig to 'jp' 
  }

  render() {
    return <button onClick={this.updateLanguage}>Change Language</button>;
  }
}

export default LanguageSwitcher;

How can I update the context from inside the LanguageSwitcher component ?

Correspondence answered 8/12, 2016 at 1:35 Comment(8)
Have you read this? facebook.github.io/react/docs/context.html#updating-context Perhaps this is something more well suited for state not contextBeisel
@Beisel Yes.. In that doc the context is updated from the component itself or there is blog link added in the doc which contains the context passed as a props to the context provider I need to update it from child componentCorrespondence
Uhh no the document says to not use context if you need to update it. "Don't do it" to be precise. I'll reiterate, use state not contextBeisel
update for others: the approach may have changed since @azium's comment as the document does provide a way to update the context from a child component: "It is often necessary to update the context from a component that is nested somewhere deeply in the component tree. In this case you can pass a function down through the context to allow consumers to update the context."Bulgar
oh yeah, also the code in this question is deprecated. the new context api in 16.3 is stable and much better than thisBeisel
@LondonRob what kind of canonical answer are you looking for? IMO the content of the docs looks just fine to me. If you want to set the context in a child, just create a setter in the provider's component and pass that to a child consumer. Then call that setter in the child consumer and set it to whatever data is within the child. Still keeps with React's idea of lifting data up.Shinny
@Beisel just a heads up to others reading this comment all these years later. Updating the context from a child component is now supported and quite straightforward: hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.htmlEnrichment
Does this answer your question? How to update the Context value in a Provider from the Consumer?Boughpot
G
709

Using hooks

Hooks were introduced in 16.8.0 so the following code requires a minimum version of 16.8.0 (scroll down for the class components example). CodeSandbox Demo

1. Setting parent state for dynamic context

Firstly, in order to have a dynamic context which can be passed to the consumers, I'll use the parent's state. This ensures that I've a single source of truth going forth. For example, my parent App will look like this:

const App = () => {
  const [language, setLanguage] = useState("en");
  const value = { language, setLanguage };

  return (
    ...
  );
};

The language is stored in the state. We will pass both language and the setter function setLanguage via context later.

2. Creating a context

Next, I created a language context like this:

// set the defaults
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

Here I'm setting the defaults for language ('en') and a setLanguage function which will be sent by the context provider to the consumer(s). These are only defaults and I'll provide their values when using the provider component in the parent App.

Note: the LanguageContext remains same whether you use hooks or class based components.

3. Creating a context consumer

In order to have the language switcher set the language, it should have the access to the language setter function via context. It can look something like this:

const LanguageSwitcher = () => {
  const { language, setLanguage } = useContext(LanguageContext);
  return (
    <button onClick={() => setLanguage("jp")}>
      Switch Language (Current: {language})
    </button>
  );
};

Here I'm just setting the language to 'jp' but you may have your own logic to set languages for this.

4. Wrapping the consumer in a provider

Now I'll render my language switcher component in a LanguageContext.Provider and pass in the values which have to be sent via context to any level deeper. Here's how my parent App look like:

const App = () => {
  const [language, setLanguage] = useState("en");
  const value = { language, setLanguage };

  return (
    <LanguageContext.Provider value={value}>
      <h2>Current Language: {language}</h2>
      <p>Click button to change to jp</p>
      <div>
        {/* Can be nested */}
        <LanguageSwitcher />
      </div>
    </LanguageContext.Provider>
  );
};

Now, whenever the language switcher is clicked it updates the context dynamically.

CodeSandbox Demo

Using class components

The latest context API was introduced in React 16.3 which provides a great way of having a dynamic context. The following code requires a minimum version of 16.3.0. CodeSandbox Demo

1. Setting parent state for dynamic context

Firstly, in order to have a dynamic context which can be passed to the consumers, I'll use the parent's state. This ensures that I've a single source of truth going forth. For example, my parent App will look like this:

class App extends Component {
  setLanguage = language => {
    this.setState({ language });
  };

  state = {
    language: "en",
    setLanguage: this.setLanguage
  };

  ...
}

The language is stored in the state along with a language setter method, which you may keep outside the state tree.

2. Creating a context

Next, I created a language context like this:

// set the defaults
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

Here I'm setting the defaults for language ('en') and a setLanguage function which will be sent by the context provider to the consumer(s). These are only defaults and I'll provide their values when using the provider component in the parent App.

3. Creating a context consumer

In order to have the language switcher set the language, it should have the access to the language setter function via context. It can look something like this:

class LanguageSwitcher extends Component {
  render() {
    return (
      <LanguageContext.Consumer>
        {({ language, setLanguage }) => (
          <button onClick={() => setLanguage("jp")}>
            Switch Language (Current: {language})
          </button>
        )}
      </LanguageContext.Consumer>
    );
  }
}

Here I'm just setting the language to 'jp' but you may have your own logic to set languages for this.

4. Wrapping the consumer in a provider

Now I'll render my language switcher component in a LanguageContext.Provider and pass in the values which have to be sent via context to any level deeper. Here's how my parent App look like:

class App extends Component {
  setLanguage = language => {
    this.setState({ language });
  };

  state = {
    language: "en",
    setLanguage: this.setLanguage
  };

  render() {
    return (
      <LanguageContext.Provider value={this.state}>
        <h2>Current Language: {this.state.language}</h2>
        <p>Click button to change to jp</p>
        <div>
          {/* Can be nested */}
          <LanguageSwitcher />
        </div>
      </LanguageContext.Provider>
    );
  }
}

Now, whenever the language switcher is clicked it updates the context dynamically.

CodeSandbox Demo

Gaucho answered 28/7, 2018 at 17:46 Comment(13)
what is the purpose of the default values you initialize the context with? Aren't those defaults always overridden by the Provider?Maryland
@Maryland correct, however in case the provider passes no value, the defaults would be used by the consumer.Gaucho
Why are contexts being limited to set/get one simple value.... That seems very inefficient. A better example would be to highlight a context that has default as an object and to update the object accordingly.Apocalyptic
It's possible to use contexts for what you're mentioning. The example in the answer was return based on the question. Therefore, it contains only a single value for brevity.Gaucho
Typescript complains in my case if setLanguage has no parameters. setLanguage: (language: string) => {} works for me.Marenmarena
Best explanation with examples so far.Krute
I am not sure why even as of today (May 2021). React documentation mentions this way of updating the context using hooks.Misusage
For those using Typescript, the type of setLanguage is React.Dispatch<React.SetStateAction<string>>Anaphase
Thank you! I just wasted my time reading multiple articles on the Internet about Context but this is what I needed. So good and clear explanation.Autoionization
The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. From react docs reactjs.org/docs/context.html . However, this is a gem and many thanks for this.Moye
Sadly, this approach falls flat if you want to pass something more complex to the Context, like in my case an JSX.Element or a functional component b/c states aren't capable to evaluate these kind of data properly.Outgeneral
For anyone coming to this using the new expo-router, a good place to put it is the top-level _layout file: #76720549Altorilievo
In your answer, @DivyanshuMaithani, you are basically just setting the context values to the state variables created using useState right? So that, the state value and its setter would be accessible throughout the component tree and we won't have to pass it individually to each child. Let me know if my understanding is correct.Stringhalt
H
113

Since it is recommended by React to use functional components and hooks so I will implement it with useContext and useState hooks. Here is how you can update the context from within a child component.

LanguageContextMangement.js

import React, { useState } from 'react'

export const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
})

export const LanguageContextProvider = (props) => {

  const setLanguage = (language) => {
    setState({...state, language: language})
  }

  const initState = {
    language: "en",
    setLanguage: setLanguage
  } 

  const [state, setState] = useState(initState)

  return (
    <LanguageContext.Provider value={state}>
      {props.children}
    </LanguageContext.Provider>
  )
}

App.js

import React, { useContext } from 'react'
import { LanguageContextProvider, LanguageContext } from './LanguageContextManagement'

function App() {

  const state = useContext(LanguageContext)

  return (
    <LanguageContextProvider>
      <button onClick={() => state.setLanguage('pk')}>
        Current Language is: {state.language}
      </button>
    </LanguageContextProvider>
  )
}

export default App
Hesperian answered 9/8, 2019 at 18:30 Comment(13)
I'm doing this and my set function inside my child component is always the one we initially declared when creating the context: () => {}Multiracial
To clarify, I think in your example state.setLanguage('pk') won't do anything, since const state = useContext(LanguageContext) is outside of the LanguageContextProvider. I solved my problem by moving the provider one level up and then using useContext on a child one level below.Multiracial
In case if you don't want to move your context provider one level up can also use context consumer like this: <LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>.Hesperian
I really like the way you organize the LanguageContextMangement.js file. That's a clean way of doing things in my opinion and I'm going to start doing that now. Thank you!Enrichment
Thanks for the appreciation it really encourages me to continue doing work like this!Hesperian
I am using typescript, when I assign the value to the state ( value={state}), it complains about the my equivalent setLanguage function shouldn't accept any arguments because it doesn't accept arguments when it gets created in the context, any ideas of solving this issue? Cheers!Heirdom
I'm not quite familiar with typescript. I hope someone else will answer.Hesperian
What if I want to use the setter functions within helper functions in the component? Is there a way I can wrap the component in a consumer but still use the context above the render/return section of the component?Knowle
unfortunately, this is not working for me, when I try to consume the context in child components the 'setState' doesn't work.Guardrail
Read Alejandro's comment may be you could solve your issue by lifting the state one level up.Hesperian
your example above is working for one set function, but if there are moe then 1 every time u want set state old values will be replaced for initial state @MateenKianiVanessa
Thanks for posting this. Some of the other answers produce hard-to-debug errors in my app, but yours works like a charm!Cantaloupe
I don't get it. setLanguage depends on setState, but setState depends on initState which depends on setLanguage. How can this work?Institutor
H
21

I personally like this pattern:

File: context.jsx

import React from 'react';

// The Context 
const TemplateContext = React.createContext();

// Template Provider
const TemplateProvider = ({children}) => {

    const [myValue, setMyValue] = React.useState(0);

    // Context values passed to consumer
    const value = {
        myValue,    // <------ Expose Value to Consumer
        setMyValue  // <------ Expose Setter to Consumer
    };

    return (
        <TemplateContext.Provider value={value}>
            {children}
        </TemplateContext.Provider>
    )
}

// Template Consumer
const TemplateConsumer = ({children}) => {
    return (
        <TemplateContext.Consumer>
            {(context) => {
                if (context === undefined) {
                    throw new Error('TemplateConsumer must be used within TemplateProvider');
                }
                return children(context)
            }}
        </TemplateContext.Consumer>
    )
}

// useTemplate Hook
const useTemplate = () => {
    const context = React.useContext(TemplateContext);
    if(context === undefined)
        throw new Error('useTemplate must be used within TemplateProvider');
    return context;
}

export {
    TemplateProvider,
    TemplateConsumer,
    useTemplate
}

Then you can create a functional component, if it is a child in the tree of the provider:

File: component.jsx

import React            from 'react';
import {useTemplate}    from 'context.jsx';
const MyComponent = () => {

    // Get the value and setter from the consumer hook
    const {myValue, setMyValue} = useTemplate();

    // Demonstrate incrementing the value
    React.useEffect(() => {

        // Increment, set in context
        const increment = () => setMyValue(prev => prev + 1); 

        // Increment every second
        let interval = setInterval(increment, 1000);

        // Cleanup, kill interval when unmounted
        return () => clearInterval(interval);

    },[]) // On mount, no dependencies

    // Render the value as it is pulled from the context
    return (
        <React.Fragment>
            Value of MyValue is: {myValue}
        </React.Fragment>
    )
}
Huarache answered 3/7, 2021 at 13:29 Comment(8)
This is great, simple and to the point !Clowers
@vsync, BS. look at the line: const increment = () => setMyValue(prev => prev + 1);, the setMyValue is a state variable setter exposed from the context, and so this updates the context value every second since it is used in an interval ...Huarache
@NicholasHamilton - My apologies. I have missed that line. But I must say your interval won't work because the useEffect only runs when the component mounts, and it only mounts once. Also, I wouldn't recommend this approach of setting the state defined at the Provider- level from the `Consumer-level because it will cause a re-render of all the children, which can be really harmful when the tree of components is quite large/deep.Salleysalli
This is better than the higher answers because it keeps all the context management in the context file so consuming it is easy. For some background on the API choices here, see kentcdodds.com/blog/how-to-use-react-context-effectively. I note you don't show an example of using TemplateConsumer; for me, the useTemplate consumer hook was all I needed.Wilhite
@Salleysalli Isn't re-rendering all the children the purpose of setting the state? If I have a component to change the theme context, I want everything else to change to the new theme.Wilhite
@Wilhite - you gave one use-case of many. it's not "the purpose", as there is no clear purpose but the one a developer decides. A hammer's purpose is not only to hit nails.Salleysalli
@Salleysalli The simplest way to explain how a hammer's purpose is not to hit nails is to explain the use-case where you use it to extract nails. Can you provide a use-case where you use Context but don't want things to be aware when it changes? I can't conceive of one because to me, Context is a kind of props.Wilhite
@Wilhite -Redux is react context where you only want specific components to update for certain parts of the whole big context. That is the go-to example. Sometimes you have this one huge component with tons of sub-components, and each tiny component needs some piece of the context which the "big" root parent-component is managing, but the problem is, with context, every component that "listens" to it with useContext will get updated. That is unwanted. So, we do want to manage some huge state at some component level, bug still controlling which child gets rendered when that state changesSalleysalli
T
7

Here's my approach, based on @Nicholas Hamilton's answer but for TypeScript and applying @LIIT recommendations.

Note: I'm using Next.js and a highly opinionated setup for ESLint.

import { createContext, useContext, useMemo, useState, Dispatch, SetStateAction } from "react"

interface TemplateContextProps {
  myValue: number | null
  setMyValue: Dispatch<SetStateAction<number | null>>
}

const TemplateContext = createContext<TemplateContextProps>({
  myValue: null,
  setMyValue: (prevState: SetStateAction<number | null>) => prevState,
})

interface TemplateProviderProps {
  children: React.ReactNode
}

function TemplateProvider({ children }: TemplateProviderProps): JSX.Element {
  const [myValue, setMyValue] = useState<number | null>(null)

  const value = useMemo(() => ({ myValue, setMyValue }), [myValue, setMyValue])

  return <TemplateContext.Provider value={value}>{children}</TemplateContext.Provider>
}

const TemplateConsumer = TemplateContext.Consumer

const useTemplate = () => useContext(TemplateContext)

export { TemplateProvider, TemplateConsumer, useTemplate }

I like to initialize the value to null, it's a more dynamic approach, but you can restrict the type to just numbers and set it to 0 by default.

Toggle answered 9/7, 2022 at 17:59 Comment(0)
A
4

One quite simple solution is to set state on your context by including a setState method in your provider like so:

return ( 
            <Context.Provider value={{
              state: this.state,
              updateLanguage: (returnVal) => {
                this.setState({
                  language: returnVal
                })
              }
            }}> 
              {this.props.children} 
            </Context.Provider>
        )

And in your consumer, call updateLanguage like so:

// button that sets language config
<Context.Consumer>
{(context) => 
  <button onClick={context.updateLanguage({language})}> 
    Set to {language} // if you have a dynamic val for language
  </button>
<Context.Consumer>
Adonis answered 28/1, 2020 at 6:58 Comment(0)
P
4

/ context.js

Setup global context file

import { createContext } from 'react'

export const Context = createContext()

/ parent.js

import { useState } from 'react'
import { Context } from './context'
import Child from './child.js'

export default function Parent() {

  const [context, setContext] = useState('default context value')

  return <>

    <Context.Provider value={[context, setContext]}>

      <Child/>

    </Context.Provider>
      
  </>

}

/ child.js

Inside child.js you can use setContext which affects both of parent.js and child.js

import { useContext } from 'react'
import { Context } from './context'

export default function Child() {
    
      const [context, setContext] = useContext(Context)

      setContext('New value set by child')

      return <>
          ...
      </>
}

Reference: How to useContext and set value of context in child components in 3 steps

Paulitapaulk answered 27/4, 2023 at 10:35 Comment(0)
S
2

To update a React context:

  1. Consume the previous context through useContext (see "mid-level" in below code example)
  2. Re-wrap the children with same context Provider
  3. Set a new value prop (for the context), derived from previous context value (via useContext)

Read the below example code from bottom-to-top, starting from:
render stage 👉 App component 👉 MidLevel component 👉 Dummy component

const {useState, Fragment, createContext, useContext, Provider} = React

// create a context
const MyContext = React.createContext()

// Dummy - a simple component which uses the context
const Dummy = () => {
  const ctx = useContext(MyContext)
  
  // print contexy
  return <p>
    Context value: 
    <mark>{JSON.stringify(ctx)}</mark>
   </p>
}

// Some mid-level component
const MidLevel = () => {
  const ctx = useContext(MyContext)
  
  // update ancestor context
  return <MyContext.Provider value={{...ctx, foo: 2, bar: 4}}>
    <Dummy/>
  </MyContext.Provider>
}

// Top-level component (with default context value)
const App = () => <MyContext.Provider value={{ foo: 1, baz: 3 }}>
    <MidLevel/>
    <Dummy/>
</MyContext.Provider>

// Render 
ReactDOM.render(<App />, root)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" style="font: 20px Arial"></div>
Salleysalli answered 14/12, 2022 at 17:44 Comment(0)
L
0

Just wanted to add to Divyanshu Maithani's answer that it's generally safer to use useMemo when wrapping the consumer in a provider.

const App = () => {
  const [language, setLanguage] = useState("en");

  const value = useMemo(
    () => ({ language, setLanguage }),
    [language, setLanguage ],
  );

  return (
    <LanguageContext.Provider value={value}>
      <h2>Current Language: {language}</h2>
      <p>Click button to change to jp</p>
      <div>
        {/* Can be nested */}
        <LanguageSwitcher />
      </div>
    </LanguageContext.Provider>
  );
};

from react/jsx-no-constructed-context-values rule :

React Context, and all its child nodes and Consumers are rerendered whenever the value prop changes. Because each Javascript object carries its own identity, things like object expressions ({foo: 'bar'}) or function expressions get a new identity on every run through the component. This makes the context think it has gotten a new object and can cause needless rerenders and unintended consequences.

This can be a pretty large performance hit because not only will it cause the context providers and consumers to rerender with all the elements in its subtree, the processing for the tree scan react does to render the provider and find consumers is also wasted.

Liquidize answered 4/6, 2022 at 21:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.