133

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 ?

falinsky
  • 6,305
  • 3
  • 25
  • 50
mshameer
  • 1,831
  • 2
  • 10
  • 15
  • Have you read this? https://facebook.github.io/react/docs/context.html#updating-context Perhaps this is something more well suited for state not context – azium Dec 08 '16 at 01:51
  • @azium 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 component – mshameer Dec 08 '16 at 03:36
  • 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 context – azium Dec 08 '16 at 05:15
  • 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." – bill Jun 08 '18 at 14:35
  • oh yeah, also the code in this question is deprecated. the new context api in 16.3 is stable and much better than this – azium Jun 13 '18 at 16:10
  • 2
    @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. – Andrew Li Jul 28 '18 at 17:54
  • 2
    @azium 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: https://hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html – lustig Mar 25 '20 at 13:48

3 Answers3

327

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

Divyanshu Maithani
  • 9,464
  • 1
  • 31
  • 41
  • what is the purpose of the default values you initialize the context with? Aren't those defaults always overridden by the `Provider`? – ecoe Jan 21 '19 at 02:18
  • @ecoe correct, however in case the provider passes no `value`, the defaults would be used by the consumer. – Divyanshu Maithani Jan 21 '19 at 05:51
  • 1
    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. – AlxVallejo Mar 28 '20 at 00:20
  • 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. – Divyanshu Maithani Apr 06 '20 at 09:44
  • 3
    Typescript complains in my case if setLanguage has no parameters. `setLanguage: (language: string) => {}` works for me. – alex351 Jul 15 '20 at 21:18
  • 1
    Thank you for this. This has been the clearest how-to that hasn't made me want to pull out my hair. – c0dezer019 Nov 29 '20 at 20:18
  • 1
    Fantastic answer. Thanks a lot for explaining it like this. – Koushik Das Mar 31 '21 at 14:14
  • Best explanation with examples so far. – informer Apr 28 '21 at 14:24
  • I am not sure why even as of today (May 2021). React documentation mentions this way of updating the context using hooks. – Aswin Prasad May 03 '21 at 22:03
59

The Above answer by Maithani is a great solution but that is a class component without the use of hooks. 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
Mateen Kiani
  • 1,303
  • 1
  • 11
  • 16
  • 2
    I'm doing this and my set function inside my child component is always the one we initially declared when creating the context: `() => {}` – Alejandro Corredor Aug 29 '19 at 13:40
  • 5
    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. – Alejandro Corredor Aug 29 '19 at 13:51
  • 2
    In case if you don't want to move your context provider one level up can also use context consumer like this: ` {value => /* access your value here */} `. – Mateen Kiani Aug 30 '19 at 04:09
  • 3
    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! – lustig Oct 08 '19 at 12:04
  • 2
    Thanks for the appreciation it really encourages me to continue doing work like this! – Mateen Kiani Oct 10 '19 at 14:14
  • 1
    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! – He Wang Oct 23 '19 at 05:48
  • I'm not quite familiar with typescript. I hope someone else will answer. – Mateen Kiani Oct 23 '19 at 08:12
  • 1
    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? – Brady Dowling Feb 03 '20 at 16:32
  • 2
    unfortunately, this is not working for me, when I try to consume the context in child components the 'setState' doesn't work. – Yuri Ramos Feb 21 '20 at 18:50
  • Read Alejandro's comment may be you could solve your issue by lifting the state one level up. – Mateen Kiani Feb 22 '20 at 12:06
3

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>
struensee
  • 366
  • 2
  • 14