How did I re-render: Sharing State through React-Context
It is a common pattern to “lift the state” up the component tree to a “Least-Common Ancestor” React Component if a state is shared amongst multiple sibling Components.
But, the child component that should not be functionally dependent upon the ‘lifted’ state will re-render when the state changes.
Another concern with a “lifted state” is to pass the state down the component tree recursively through component props. (Commonly known as ‘prop-drilling’).
React Context API is an amazing utility that is either overlooked or misused, in favor of a global state-management library that utilizes a huge store, which is available across the entire application.
Though using additional libraries is not wrong, and solves the problem of manually passing down the state to Child Components via props, usage of these global state-management libraries often leads to complications like:
- Usage of Higher-Order Components to provide access to the Store.
- Maintaining State Hierarchy.
- Tracing interactions between reducers, actions, and dispatch calls.
React Applications should be designed without using a global store in favor of the Component’s local state. Meanwhile React Context could be used to share global data like UI preferences, and expose global methods like Authentications.
React Context is not a State-Management Tool. It is a transport mechanism.
Let’s see the benefits of React-Context through a generic use case, an “Off-Canvas Sidebar Menu”. The solutions are to be assessed over prop-drilling avoidance and reduction in unnecessary re-renders.
Off-canvas Sidebar Menu
An off-canvas Sidebar Menu is a complete Component of its own, whose visibility is controlled by a button that exists outside of the Component.
Therefore, the visibility state needs to be ‘lifted’ up the component tree. To understand the scenario better, let's start designing it step by step.
Please view the
console
in each example to understand the re-rendering Impact of each action.
Menu Design with a Lifted-State
In a mock-up of the aforementioned scenario, the App
Component acts as the root component of the page with Nav
& Aside
Components as its children.
The show
the state is lifted to App
and is then passed down to the components via props. Upon clicking the menu button the console prints the following:
>> Rendering NAV
>> Rendering ASIDE
>> Rendering APP
This solves none of the issues. The Entire Component Tree is re-rendering and props need to be manually passed down.
Menu Design by React Context
The component Tree can be redesigned by adding a React Context and passing the state variables through it. Upon clicking the menu button and checking the console, it prints the following:
>> Rendering NAV
>> Rendering ASIDE
>> Rendering APP
Usage of React Context does solve ‘prop-drilling’, but the entire Component Tree still suffers from excessive re-rendering.
Menu Design by Stateful Higher-Order Context
The visibility-state logic does not pertain to App
Component, so moving it entirely into the Context Provider might save App
from re-rendering.
Upon checking the console after pressing the menu button, it prints the following:
>> Rendering NAV
>> Rendering ASIDE
Usage of stateful React Context saves the Parent Component from re-rendering and avoids prop-drilling, but there is still unnecessary re-rendering of Nav Component.
Menu Design by Stateful Higher-Order Dual-Context
Upon deeper inspection, we realize that the Nav
component does not require the show
state, it only needs setShow
to change the state.
Redesigning the Component Tree by using different Contexts for the state variables and state-update functions, in the Higher-Order Component for Context.
Upon checking the console after pressing the menu button, it prints the following:
>> Rendering ASIDE
Usage of stateful React Context prevents unnecessary re-rendering and avoids prop-drilling.
Both issues of prop-drilling & unnecessary re-rendering of App
and Nav
Components are solved via using “Stateful Higher-Order Dual-Context”.
Conclusion
From the above activity, it could be concluded that while React Context is good at solving prop-drilling, careful designing is required to prevent unnecessary re-renders. The appropriate usage of React Context demands the following:
- Always design a Higher-Order-Component for the Context-Providers.
- Always keep state variables and state-update functions in separate Contexts.
- Always pass the context-consuming Components either as a child component or as props, to the Higher-Order-Component for the Context-Providers.
The reasons for aforementioned conditions are:
— The “Stateful Higher-Order Dual-Context” paradigm consumes Components via
children
prop. This prevents re-rendering of child Components, even if Parent Component re-renders.
Read more on https://kentcdodds.com/blog/optimize-react-re-renders— State-update Functions do not change their reference between renders. So, keeping it separate than state variable benefits the Components that only needs to consume the state-update functions.
Read more about the usage of React Context on:
https://kentcdodds.com/blog/how-to-use-react-context-effectively
https://kentcdodds.com/blog/how-to-optimize-your-context-value
Other articles in the series