reactjsreact-context

React Context API change context without re-rendering provider


I'm relatively new to React and I'm struggling with one thing. I have an app which has many nested components. I set up a Context for them. There's a child component (context consumer) that I want to be able to change the context with. And I don't want to trigger re-renders only in components that use this context.

However, I cannot really change the context value in the consumer. The only thing I was able to come up with is this:

import { createContext, useContext, useState } from "react";

interface ContextType {
  message: string;
  setMessage: (value: string) => void;
}

const Context = createContext<ContextType>({} as ContextType);

const Consumer1 = () => {
  const { message, setMessage } = useContext(Context);
  return (
    <div>
      {message}
      <button onClick={() => setMessage("bar")}>Change context</button>
    </div>
  );
};

const Consumer2 = () => {
  return <div>Consumer2</div>;
};

const App = () => {
  const [message, setMessage] = useState("foo");
  const value = { message, setMessage };
  return (
    <div>
      <Context.Provider value={value}>
        <Consumer1 />
        <Consumer2 />
      </Context.Provider>
    </div>
  );
};

export default App;

CodeSandbox link: here.

So, basically when I click the button in Consumer1, it triggers a context change, but the thing is - the context value is the App (context provider) state. That's why it works, I am basically re-rendering App, so it can pass the newly changed value via the provider. Consumer1 reads the new updated value and all is good. However, Consumer2 also gets re-rendered here, because its parent got re-rendered.

I guess my question is simple: is there a way to trigger a context change in the consumer without re-rendering the provider (which is usually a parent for many components) - so the only components re-rendered are the ones that use the context? Or isn't that possible?


Solution

  • It is not possible to update a state without causing re-render.

    But what you can do is to separate that logic in different provider component, rather than the App itself.

    const MessageProvider = ({children}) => {
      const [message, setMessage] = useState("foo");
      const value = { message, setMessage };
    
      return <Context.Provider value={value)>{children}</Context.Provider>
    }
    

    and then use it wherever you like

    const App = () => (
       <MessageProvider>
          <Consumer1>
          <Consumer2>
          <SomethingThatWontRerender />
          <SomethingElse />
       </MessageProvider>
    )
    

    This way you will rerender only the provider and the consumers