How did I re-render: Asynchronously Read State of a React Component

Bhavya Saggi
4 min readJul 19, 2022

--

A React Component that subscribes to a React State is bound to re-render upon any update to the state. But, what if a component only needs to read the state without performing any updates to the DOM?

Component B only needs to reads to consume the state without making any visible changes to DOM.

Asynchronous read of a React State could be defined as reading a state value without subscribing to any state updates.

Problem Statement

While React is built around the notion of ‘reactivity’ and asynchronous access to a state goes against it. The asynchronous access to a react state still is required in a few use-cases like “accessing a shared react state which is consumed by a user action in a render-heavy component like maps or canvas”.
To emulate such a case, consider a scenario with the following constraints:

  1. React ComponentA & ComponentB, need to share a React State and consume the state to display content & perform updates to it
  2. A React ComponentX needs the state value to perform background processes that are not visible to the user. For e.g. submitting state value to an endpoint on a button click.
  3. Update to ComponentX is expensive as it renders a large amount of data.
Asynchronous access to a React State would enhance the user experience by reducing re-renders.

Since, ComponentX does not need state value to display any content and only requires it for data-processing on user-interaction, it could be said that ComponentX needs to asynchronously read the React state.

Design Paradigm

An escape hatch for this scenario could be designed by sharing a React State with a React Ref, & writing a getter & setter functions for accessing the value.

  1. Synchronize the update to both state and the ref through a setter updateState function (maintaining its reference via a useCallback hook).
  2. Create a getter getState function (maintaining its reference via a useCallback hook) to get value from the Ref.
  3. Use nested React Contexts to pass the [getState, updateState] & [state, updateState]. (maintaining references via useMemo hook).
  4. Create custom React Hooks to access value for each React Context created, and utilize them as per need, i.e. [getState, updateState]to be used whenever asynchronous access to state variable is required, and [state, updateState] to be used whenever synchronous access to state variable is required.

Conceptually, the design would look something like the following:

Synchronized React State & Ref, for synchronous and asynchronous access.

Live Example

For better understanding and to analyze the problem visually, consider a “Number Guessing Game”, where:

  1. The user is shown the digit at tens place, and the digit at ones place.
  2. The user may click ‘Randomize’ for a new number to guess, and click on the button with the correct number to win.
  3. Upon clicking the button with the correct number, a success message is shown. Otherwise, a failure message is shown.

Since the Option Buttons do not actively require the value of the current number, and only require the number when clicked on, we may preserve the re-rendering of 100 buttons by reading the number asynchronously.

If the aforementioned approach is followed, the code would look something like this:

Refer to the console to view the components re-rendered on each action.

As it can be viewed from console for the above example, Only ComponentA & ComponentB are re-rendered whenever the number (React State) updates. Furthermore, on clicking any of the numbers no re-rendering through the usage of the asynchronous getter function.

It is to be noted, that if not utilized properly this approach may create more problems than the ones it solves. Therefore it is not advisable for general use cases, e.g. the value fetched by the getter function (getState)may not be accurate and could be out of sync with the updated state value.

The custom hooks for accessing the state variable would only be usable if the Context Provider is an ancestor component.

A significant performance boost is only visible when there is a large re-render cost. So, always implement the simple approach first, measure, and only optimize if necessary.

Alternatives

Simpler alternatives could be used to gain similar effects

  1. Breaking a large render-heavy React Component into smaller components, memoizing the smaller child components, and accessing the state only where it is needed to render something.
  2. If the React State is serializable, it can be rendered in the data attribute for an ancestor DOM Node, and access the state value via DOM Node’s reference (via useRef hook ). But be cautioned as this approach may pollute the DOM.
  3. Atomic State Management, via third-party libraries such as recoil or jotai, come with great utilities which include persisting a state in localStorage, subscribing to a sub-state via optics, asynchronous setter, and getter for the state, transactions, snapshots, etc.!

--

--