Before diving into the useSyncExternalStore
API
let us get familiar with the terms
which would be useful to understand the new hook.
Concurrent rendering and startTransition API
Concurrency is the mechanism to execute multiple tasks simultaneously by prioritizing the tasks. This concept is explained in an easy way by Dan Abramov with an analogy of phone calls.
We can opt-in to keep the app responsive while rendering with the help of the startTransition API. In other words, React can now render with pauses. This allows browsers to handle events in between.
Check out more details on the startTransition API, which we have written in our previous post.
External store
The external store is something which we can subscribe to. Examples of the external store include Redux store, Zustand store, global variables, module scope variables, DOM state, etc.
Internal stores
Internal stores include props, context, useState, useReducer.
Tearing
Tearing refers to visual inconsistency. It means that a UI shows multiple values for the same state.
Before React 18, this issue did not come up. But in React 18, concurrent rendering makes this issue possible because React pauses during rendering. Between these pauses, updates can pull in the changes related to the data being used to render. It causes the UI to show two different values for the same data.
Let us consider the example mentioned in the WG discussion of tearing.
Here, a component needs to access some external store to get the color.
With synchronous rendering, the color rendered on UI is consistent.
In concurrent rendering, initially, the color fetched is blue. React yields, and the store gets updated to red. React continues rendering with the updated value red. It causes inconsistency in UI, which is known as ‘tearing’.
To fix this issue,
the React team added useMutableSource hook
to safely and efficiently read from a mutable external source.
But, members of the working group
reported
flaws with the existing API contract
that make it difficult for library maintainers to adopt useMutableSource in their implementations.
After a lot of discussions,
the useMutableSource
hook was redesigned and renamed to useSyncExternalStore
.
Understanding useSyncExternalStore hook
The new useSyncExternalStore hook available in React 18 allows to properly subscribe to values in stores.
To help simplify the migration, React provides a new package use-sync-external-store. This package has a shim that works with any React, which has support for hooks.
useSyncExternalStore hook takes two functions
- ‘subscribe’ function to register a callback function
- ‘getSnapshot’ is used to check if the subscribed value has changed since the last time, it was rendered, It either needs to be an immutable value like a string or number, or it needs to be a cached/memoized object. The immutable value is then returned by the hook.
A version of the API with automatic support for memoizing the result of getSnapshot:
Let us check out the example discussed in a React 18 for External Store Libraries talk by Daishi Kato.
If we use startTransition
somewhere in the code,
it may lead to tearing.
To fix the tearing issue we can now use the useSyncExternalStore
API.
Let us modify the useStore
hook of the library
to use useSyncExternalStore
instead of the useEffect
and useState
hooks.
The code looks clean, maintainable, and safe with the new hook.
Migration to the useSyncExternalStore
hook in external stores
is easy and recommended to avoid any potential issues.
What kinds of libraries are affected by concurrent rendering?
-
Libraries that have components and custom hooks which don’t access external mutable data while rendering and only pass information using React props, state or context, are not affected.
-
The libraries which deal with data fetching, state management, or styling (Redux, MobX, Relay) are affected. It is because these libraries store their state outside of React. With concurrent rendering, those data stores can be updated in the middle of rendering, without React knowing about it.
To know more about the useSyncExternalStore
hook,
read through the list of links we have compiled -