Search code examples
javascriptreactjsreact-contextreact-functional-component

React context; child component not rerendering


I may be misunderstanding how react context work in general, but how I think it should work goes like this:

I have a main app that has the Context provider as following

export default function app(){
   return <contextObj.Provider value={Complex Object with multiple properties...}>
     <Main/>
   </contextObj.Provider>
}

My main app is as follows

export default function main(){
   //Have imported the context class and am using it
   let store = React.useContext(myContext)
   
   return <React.Fragment> 
     <comp1 max={store.valueX} />
     <comp2 />
   </React.Fragment>
}

Inside of comp2, modify valueX inside of the context objext

export default function comp2(){
   //Have imported the context class and am using it
   let store = React.useContext(myContext)

   return <buttonComp onClick={()=>store.valueX=3} />
}

So in theory comp1 should receive the updated value and re-render? This is where I'm stuck, as altering the context property isn't causing my comp1 to re-render.


Solution

  • Just like normal component state, you can't just directly mutate (assign to) the value of the context. You'll need to contain the value in state and pass down a callback to change that state (or use useDispatch for example):

    export default function App() {
      const [value, setValue] = useState({
        /* Complex Object with multiple properties */
      });
    
      const updateX = (newX) =>
        setValue((prevValue) => {
          return { ...prevValue, valueX: newX };
        });
    
      return (
        <contextObj.Provider value={{ ...value, updateX }}>
          <Main />
        </contextObj.Provider>
      );
    }
    
    export default function Comp2() {
      //Have imported the context class and am using it
      let store = React.useContext(myContext);
    
      return (
        <ButtonComp
          onClick={() => {
            store.updateX(3);
          }}
        />
      );
    }
    

    You'll probably want to move the state and the callbacks into a component specifically made for this - e.g. MyContextProvider which just contains the state and wraps its children in MyContext.Provider.

    export function MyContextProvider({ initialValue, children }) {
      const [value, setValue] = useState(initialValue);
    
      const updateX = (newX) =>
        setValue((prevValue) => {
          return { ...prevValue, valueX: newX };
        });
    
      return (
        <MyContext.Provider value={{ ...value, updateX }}>
          {children}
        </MyContext.Provider>
      );
    }
    
    export default function App() {
      return (
        <MyContextProvider
          initialValue={/* Complex object with multiple properties */}
        >
          <Main />
        </MyContextProvider>
      );
    }
    

    However, if you're deriving your complex object with multiple properties inside App, it may be easier to just use the Context.Provider directly like in my first example.

    Also, remember to always capitalize your components' names:

    Note: Always start component names with a capital letter.

    React treats components starting with lowercase letters as DOM tags. For example, <div /> represents an HTML div tag, but <Welcome /> represents a component and requires Welcome to be in scope.

    To learn more about the reasoning behind this convention, please read JSX In Depth.