How to update the Context value in a Provider from the Consumer?

JavascriptReactjsReact Context

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

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

Solution 3 - Javascript

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

https://stackoverflow.com/questions/49809884/access-react-context-outside-of-render-function/49810870#49810870

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);

Solution 4 - Javascript

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

Solution 5 - Javascript

@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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNowshad SyedView Question on Stackoverflow
Solution 1 - JavascriptJacobo JaramilloView Answer on Stackoverflow
Solution 2 - JavascriptJack VialView Answer on Stackoverflow
Solution 3 - JavascriptShubham KhatriView Answer on Stackoverflow
Solution 4 - JavascriptRam Kumar ParthibanView Answer on Stackoverflow
Solution 5 - JavascriptZus CView Answer on Stackoverflow