5

I've seen a few questions related to this topic, but none that tackle the issue head-on in a pure way. useContext is a great tool for minimizing prop-drilling and centralizing key data needed across your app; however, it comes at a cost that I'm trying to minimize.

The closest issue to the one I'm describing here was asked two years ago here. The question didn't gain a lot of traction and the only answer basically says call the context in a parent container and pass values down through props (defeats the purpose of context) or use Redux. Maybe that's the only way, but I wanted to bring the question back to the collective to see if there is a better answer that's not dependent on external libraries.

I've set up a code sandbox here to illustrate the issue. Re-renders are console logged out to make seeing them easier.

In short, when a state changes in context, every app accessing data from that context re-renders, even if it's not utilizing the state data that changed (because it's all ultimately passed through the value object). React.Memo does not work on values accessed from context the same way it does for properties.

For example, in the code sandbox linked above, in App.js if you comment out <AppContainerWithContext /> and load <AppContainer />, there are two states managed in the container, one called titleText and the other called paragraphText. titleText is passed as a prop to component called TitleText and paragraphText is passed to a component called ParagraphText. Both components are wrapped in React.memo(). There are two buttons called in the AppContainer and each has a function that changes the text back and forth based on the value of separate boolean states.

Here is the function that toggles the titleText, the one for paragraph text is the same logic:

  const changeTitleHandler = useCallback(() => {
    const title = listTitleToggle ? "Title B" : "Title A";
    setListTitleToggle((pV) => !pV)
    setTitleText(title);
  }, [listTitleToggle]);

Since the titleText component and paragraphText components are wrapped with React.useMemo, they only re-render when the corresponding value passed to them changes. Perfect.

Now in App.js if you comment out the <AppContainer /> component and enable the <AppContainerWithContext /> component, the rendered output and result of button clicks is identical; however, the states that change and are rendered on the screen are now managed by AppContext (called contextTitleText and contextParagraphText and passed to the TitleText component and ParagraphText component via useContext.

Now, if you click on the button to toggle the title, the ParagraphText component re-renders too, even though it doesn't use the contextTitleText state. My understanding of why this happens is because the value object changes when the contextTitleText is updated, causing any component accessing that value object through useContext to re-render.

My question is this:

Is there a way to utilize useContext without causing re-renders on all components accessing the context. In the example above, can we utilize useContext to manage the contextTitleText and the contextParagraphText but only re-render the components where the state from context being accessed changes?

RyanY
  • 635
  • 1
  • 8
  • 20
  • Related: [Avoid runnning an effect hook when Context get updated](https://stackoverflow.com/questions/66543656/avoid-runnning-an-effect-hook-when-context-get-updated/66544925#66544925) and [Split up Context into state and update to improve performance (reduce renders)?](https://stackoverflow.com/questions/66717457/split-up-context-into-state-and-update-to-improve-performance-reduce-renders/66723145#66723145) These two split the context between methods and values, but there's no reason you couldn't split it along different lines, or create separate contexts as needed. – pilchard May 01 '22 at 22:18
  • Yeah, I guess you could generate several contexts, each grouped by which components need access to their values. As long as you wrap your values in useMemo, then the components accessing those values wouldn't re-render unless the actual value changed. I wonder what the performance costs are of using several contexts in a file instead of just one? – RyanY May 01 '22 at 22:33
  • It's very common to use multiple contexts either nested or branched and helps to segment data in meaningful ways. see: [Is nesting React Context Provider and consuming those with useContext a problem?](https://stackoverflow.com/questions/55205472/is-nesting-react-context-provider-and-consuming-those-with-usecontext-a-problem) – pilchard May 01 '22 at 22:34
  • It would be nice if there was more input on this question, as I've faced something similar. I've tried using a ref for the context object, and just modifying the properties as needed, so the same object would be passed each time, but no luck. I'm actually surprised it rerendered at all since I think it only does a shallow check, and it should see the same object every time. – Jordan Jul 28 '22 at 01:05

0 Answers0