Sébastien Lorber
Sébastien Lorber

Sébastien Lorber

React 18 milestone: React-Redux adopts useSyncExternalStore

Subscribe to my newsletter and never miss my upcoming articles

This is a sample from my This Week In React newsletter. Subscribe for more!

This Week In React newsletter banner


React-Redux v8 alpha.0 was just announced by Mark Erikson.

The library is now fully rewritten in TypeScript.

More importantly, React-Redux v8 is adopting a new React 18 hook useSyncExternalStore (replacing useMutableSource).

This hook allows React to work properly in concurrent mode and sync with an external state (from Redux) without tearing (the UI can't become inconsistent with store state).

This hook is the "level 2" (make it right) of the state subscription ladder:

image.png

Dan Abramov explains it's an important milestone for React 18:

For the first time we’ve been able to move the “meat” of the react-redux bindings implementation into react itself. It wasn’t a lot of code, but it relied on a bunch of hacks and complexity that kept growing. Now that “meat” is 5 lines of code, we handle the rest.

import { useSyncExternalStoreExtra } from 'use-sync-external-store/extra';

// React-Redux v8 alpha code in useSelector()
const selectedState = useSyncExternalStoreExtra(
  store.subscribe, 
  store.getState, 
  // TODO Need a server-side snapshot here
  store.getState, 
  selector, 
  equalityFn
);

To ease incremental adoption in libraries today, an external package use-sync-external-store has been published, allowing to use this hook with a consistent API on React 16, 17 & 18.

Unlike the former useMutableSource API, this new hook supports unstable, inline selectors without re-subscribing, and you won't need to wrap selectors in useCallback to stabilize them:

// Not ideal
const user = useSelector(
  useCallback(state => state.user,[])
);

// Simpler
const user = useSelector(state => state.user)

The community remains very interested to have a native useContextSelector() hook in React core.

A performant useContextSelector() hook would allow Redux to try again passing the store state directly as context value, instead of passing a store object and managing subscriptions internally. React would now hold entirely the Redux state and the React-Redux bindings would move to the ladder's stage 3 (make it fast). This stage would permit React to avoid unnecessary rendering work when a Redux store update happens while a concurrent rendering is in progress.

useContextSelector is still under research and is not a mandatory feature for React 18 to land. Andrew Clark commented that it will more likely be released in a minor 18.x release.

 
Share this