Context, however, allows us to cheat a little bit and bypass that tree of components. If we just extract this data that we want to pass around to context and use it in the pink component directly, then only the component with the data itself and the component that actually uses it will re-render. The rest of the app will just sit there quietly and do absolutely nothing.
Okay, kittens are fun, but let's take a bit closer look at normal life and normal components. Let's say I want to implement a page that looks like this. Two-page layout navigation on the left, main content area on the right. In navigation I want to have a button that expands and collapses navigation. And I want to render different number of blocks at the bottom of the content area depending on whether navigation is expanded and collapsed. If I start implementing it with normal props, it would look something like this. We would have a page that holds navigation state, passes that state to the button that expands it through navigation component and listens to the callback on it. And then we would have to pass this data to the page component through all the layers down, down, down to the level where those blocks that I need to render. And again, from re-render's perspective it's not great. Every navigation expanding or collapsing will result in re-renders of absolutely everything.
What I can do instead here is to extract this expand collapse logic into context, only logic, nothing more. This would look something like this, same state that controls navigation, we would have a value to which we would pass the state itself and the toggle function, and then we would pass this value to context provider. Suddenly our page, instead of tons of code and props everywhere, it just turns into something as clean as this. Context at the top, navigation, and content passed to it as children. And then somewhere in navigation, we would have the button that just uses toggle function directly from context. And somewhere down the content area, we would have the block with items that use the state of navigation directly from context.
But although this example looks cool in theory, in real life it's of course a little bit more complicated, as always. In real life, we still have a problem of context having a bad reputation for re-renders. Mostly this reputation comes from two facts. The first is that context-related re-renders will be triggered in every child that happens to use this context hook, regardless of whether it uses the actual data or not. So in our case, the component on the left that uses only toggle function, in theory, shouldn't care about state at all, but in practice, both of those components will re-render when context changes. And the second problem here is that those re-renders are unstoppable. There is no sane way to prevent them. Use memo or use callback hooks won't help. There is, however, another trick that can help here if this behavior actually causes performance problems. We can just split those context providers into two. Instead of just one single value that holds both toggle and the state, we can have two separate values.
Comments