125

MyContext.js

import React from "react";

const MyContext = React.createContext('test');
export default MyContext;

I created my context in a separated js file where I can access my parent as well as my child component

Parent.js

import MyContext from "./MyContext.js";
import Child from "./Child.js";

class Parent extends Component {

    constructor(props) {
      super(props);
      this.state = {
        Message: "Welcome React",
        ReturnMessage:""
      };
    }
    
    render() {
        return (
           <MyContext.Provider value={{state: this.state}}>      
              <Child /> 
           </MyContext.Provider>
       )
    }
}

So I created the parent component with a Provider context and calling child component in the provider tab

Child.js

import MyContext from "./MyContext.js";

class Child extends Component {

    constructor(props) {
      super(props);
      this.state = {        
        ReturnMessage:""
      };
    }
    
    ClearData(context){
        this.setState({
           ReturnMessage:e.target.value
        });
        context.state.ReturnMessage = ReturnMessage
    }

    render() {
        return (
           <MyContext.Consumer>                 
              {(context) => <p>{context.state.Message}</p>}
              <input onChange={this.ClearData(context)} />
           </MyContext.Consumer>
       )
    }
}

So in child by using the Consumer, I can display the data in child rendering part.

I'm facing an issue when I want to update the state from the consumer.

How to update provider state or manipulate state of provider?

Nowshad Syed
  • 1,293
  • 3
  • 9
  • 6

5 Answers5

133

You could use the useContext hook to achieve this. It's quite easy to use it in the child elements of the Provider. As an example...

authContext.js

import { createContext } from "react";

const authContext = createContext({
  authenticated: false,
  setAuthenticated: (auth) => {}
});

export default authContext;

Login.js (component consuming the Context)

import React, { useContext } from "react";
import authContext from "./authContext";

export default () => {
  const { setAuthenticated } = useContext(authContext);
  const handleLogin = () => setAuthenticated(true);
  const handleLogout = () => setAuthenticated(false);

  return (
    <React.Fragment>
      <button onClick={handleLogin}>login</button>
      <button onClick={handleLogout}>logout</button>
    </React.Fragment>
  );
};

Finally the index.js

import ReactDOM from "react-dom";
import React, { useState } from "react";

import authContext from "./authContext";
import Login from "./Login";

const App = () => {
  const [authenticated, setAuthenticated] = useState(false);

  return (
    <authContext.Provider value={{ authenticated, setAuthenticated }}>
      <div> user is {`${authenticated ? "" : "not"} authenticated`} </div>
      <Login />
    </authContext.Provider>
  );
};

ReactDOM.render(<App />, document.getElementById("container"));

As you can see, it becomes quite easy to consume the data stored in the context using the useContext hook. Of course, as with every React hook, it only works with functional components.

If you want to see the code working. https://codesandbox.io/s/react-playground-forked-wbqsh?file=/index.js

Jacobo Jaramillo
  • 1,469
  • 1
  • 8
  • 5
  • 34
    I'm struggling to understand how the setAuthenticated function updates the context when the param was just tossed. Every context 'updater' function I've seen is basically an empty function/result and seems like a 'do-nothing' function. How does this work?! – Lo-Tan Feb 25 '21 at 14:27
  • 14
    It reads like magic. Why `setAuthenticated: (auth) => {}` is empty? Same question as Lo-Tan. How does it work? – tejasvi88 Jul 14 '21 at 07:06
  • 1
    Thirded - is it impossible to set multiple values at once? Do we need to make a magic setter for every value? – bonzairob Jul 16 '21 at 11:13
  • @bonzairob ` const [infoData, setInfoData] = useState({ name: "John Doe", age: "24", sex: "M" }); ` – Guillaume Caggia Jul 23 '21 at 15:58
  • 15
    @tejasvi88 `setAuthenticated: (auth) => {}` is just a placeholder. You provide the function here: `value={{ authenticated, setAuthenticated }}`. – Data Mastery Sep 14 '21 at 12:02
  • 17
    I needed to expand on @DataMastery's comment, because I spent a full 15 minutes struggling with this. The state is still handled in the parent component, but before you can pass `setAuthenticated` from `useState` to `authContext.Provider`, you need to define the shape of `setAuthenticated` on the context. The easiest way to do this is to make an empty function that accepts the parameters, to be replaced later by the setState function. Hope that saves you 15 minutes! – Joe Sadoski Sep 28 '21 at 19:52
42

Updating Context from a Nested 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:

theme-context.js

// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
});

theme-toggler-button.js

import {ThemeContext} from './theme-context';

function ThemeTogglerButton() {
  // The Theme Toggler Button receives not only the theme
  // but also a toggleTheme function from the context
  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (
        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default ThemeTogglerButton;

app.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // State also contains the updater function so it will
    // be passed down into the context provider
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // The entire state is passed to the provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);

The above example is straight from the React Context API docs v16.8.6, and is the recommended way to update a context value from a consumer. https://reactjs.org/docs/context.html#updating-context-from-a-nested-component

Jack Vial
  • 2,354
  • 1
  • 28
  • 30
  • 5
    What is the purpose of the default context value, considering that the Context Provider will always set it anyways? – Sebastien De Varennes Apr 23 '20 at 21:40
  • @SébastienDeVarennes you've got a point but it would be easier to recognize what value does if default value is set. – JunKim Jul 27 '20 at 08:48
  • Can we not just change the context from a JS file, like we do in simple JS objects? – pikachu_on_acid Oct 31 '20 at 05:25
  • 2
    I am new to this and wondering if calling setState() wouldn't re render everything from the top for the App component? What if App component contains other expensive components which wouldn't want to re render? – Akash Gorai Jan 16 '21 at 12:25
  • Thank you for the post and for linking those docs. Very helpful indeed and a clear example to illustrate the mechanics. – Tim Consolazio Apr 09 '21 at 16:34
  • @AkashGorai did you find an answer for this? Does the entire tree rerender when we update the state in App? – Sri Jul 02 '21 at 04:28
  • 1
    Yes, it does rerender the entire tree. The Context.Provider rerenders when its children prop change in subsequent render cycles. – Akash Gorai Jul 03 '21 at 13:43
  • You are assigning theme.background but i cannot see in your code where theme.background is defined - it is not fully clear - can you explain? or correct the code? – Mercury Oct 25 '22 at 06:51
35

Firstly, in order to update the context from the consumer, you need to access the context outside of the render function, For details on how to do this, check

Access React Context outside of render function

Secondly, you should provide a handler from Provider which updates the context value and not mutate it directly. Your code will look like

Parent.js

import MyContext from "./MyContext.js";
import Child from "./Child.js";

class Parent extends Component {

    constructor(props) {
      super(props);
      this.state = {
        Message: "Welcome React",
        ReturnMessage:""
      };
    }

    updateValue = (key, val) => {
       this.setState({[key]: val});
    }
    render() {
        return (
           <MyContext.Provider value={{state: this.state, updateValue: this.updateValue}}>      
              <Child /> 
           </MyContext.Provider>
       )
    }
}

Child

import MyContext from "./MyContext.js";

class Child extends Component {

    constructor(props) {
      super(props);
      this.state = {        
        ReturnMessage:""
      };
    }

    ClearData(e){
        const val = e.target.value;
        this.setState({
           ReturnMessage:val
        });
        this.props.context.updateValue('ReturnMessage', val);
    }

    render() {
        return (
           <React.Fragment>
             <p>{this.props.context.state.Message}</p>}
             <input onChange={this.ClearData} />
           </React.Fragment>
       )
    }
}

const withContext = (Component) => {
   return (props) => {
       <MyContext.Consumer>    
            {(context) => {
               return <Component {...props} context={context} />
            }}
       </MyContext.Consumer>
   }
}

export default withContext(Child);
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • Thanks for your solution Shubham Khatri, In case i need to update multiple state then in parent i will set the sate like this , how abount in child updateReturnValue = (val) => { this.setState({ state }); } – Nowshad Syed May 24 '18 at 07:17
  • @NowshadSyed, yes you can have a general function that updates all the state as well. I updated my answer for the same – Shubham Khatri May 24 '18 at 07:20
  • For nested components can i have one provider and multiple consumers For an Example : 1 is an parent , 1.1 is a child to 1 and 1.1.1 is child to 1.1, Can i have provider to 1 and consumers to 1.1 and 1.1.1 – Nowshad Syed May 24 '18 at 08:00
  • You can have as many consumers as you want which share the same context values. – Shubham Khatri May 24 '18 at 08:01
  • {this.props.context.state.Message} TypeError: Cannot read property 'state' of undefined – Nowshad Syed May 24 '18 at 09:01
  • Did you write the withContext HOC and export child after wrapping with withContext – Shubham Khatri May 24 '18 at 09:04
  • Added this in Parent component updateValue = (key, val) => { debugger; this.setState({[key]: val}); } .In Child const withContext = (Component) => { debugger;return (props) => { {(context) => {return }}}} render() { return (

    {this.props.context.state.Message}

    ) }
    – Nowshad Syed May 24 '18 at 09:20
8

You need to write a function in the Provider component to update the State. To be exact Consumer can only use the values and the function(s) you wrote in the Provider component.

In Parent Component

updateReturnMessage = (ReturnMessage) => {
  this.setState((prevState) => ({ ...prevState, ReturnMessage }))
}

<MyContext.Provider value={{ state: this.state, updateReturnMessage: this.updateReturnMessage }}>
// your code goes here
</MyContext.Provider>

In Child Component:

ClearData(e){
  const val = e.target.value;
  this.context.updateReturnMessage(val);
}

This function is similar to the action creators available in Redux and flux

  • I do that, but this.setState is undefined. the "this" is this.props of the context consumer control calling the method. I tried to use arrow (=>) function on the provider to make sure 'this' is correct, but still the same issue. Any suggestions? – Pinny Feb 04 '19 at 19:59
  • 1
    https://codesandbox.io/s/5mrk843z94. Check this link I used the context in the way you asked @Pinny – Ram Kumar Parthiban Feb 06 '19 at 10:45
-16

@nowshad, are you trying to use with redux Then I suggest using the provider

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
​
const store = createStore(todoApp)
​
render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

If you are using for just few components and you want to have values for all nested components as per your statement

For nested components can i have one provider and multiple consumers For an Example : 1 is an parent , 1.1 is a child to 1 and 1.1.1 is child to 1.1, Can i have provider to 1 and consumers to 1.1 and 1.1.1 

then I suggest that you just pass a handler down as prop and once you want to change the state call the handler and it will change values throughout your components.(This should be done if you have just few child components, who all require the same values throughout)

***Using context, we can avoid passing props through intermediate elements***

As per React Docs

Don’t use context just to avoid passing props a few levels down. Stick to cases where the same data needs to be accessed in many components at multiple levels.

Check Official Docs as to why and why not use Context: https://reactjs.org/docs/context.html

Let me know if you still have issues or doubts as to why and how to use context

Zus C
  • 99
  • 2
  • 9