Notes about React 18 RC.0
I am writing this note while React 18 is in release candidate stage. I created this note because I did not closely follow its development progress, and I’m only aware that React 18 will be released soon after the RC was published.
I studied React’s excellent test suite and compared test reports between React 17 and 18-rc.0^[The bold text in front of each entry is an internal name in React source code that you can use to learn more about the feature or change, until official documentation is available. My recomendation is to look up test files with that name, as they very precisely describe the behavior of the API.] and gathered what I learned from the tests, relevant blog posts and discussions^[At the time of writing, I wasn’t aware that React Conf 2021 happened, and that React 18 RC was released during the conference. Maybe you can go watch the keynotes instead…] in this note.
ReactDOMRoot:
ReactDOM.createRoot
supersedesReactDOM.render
.ReactDOM.render
is still available but this creates a “root” in “legacy” mode. This API is called “Legacy root API”. A legacy root behaves like React 17.// Before (Legacy Root API) ReactDOM.render(<App tab="home" />, container) // After (New Root API) const root = ReactDOM.createRoot(container) root.render(<App tab="home" />)
The new API is called the New root API. The new root API enables concurrent rendering.
This new API no longer has the ability to specify a render callback. Instead, put a
ref
callback on the target DOM node to be notified when something is mounted.- Due to concurrent rendering, the
ref
may not be assigned immediately (i.e. synchronously), unlessReactDOM.flushSync
is used.
- Due to concurrent rendering, the
ReactDOMRoot:
ReactDOM.hydrateRoot
supersedesReactDOM.hydrate
.// Before ReactDOM.hydrate(<App tab="home" />, container) // After const root = ReactDOM.hydrateRoot(container, <App tab="home" />)
ReactDOMFiberAsync: In the New root API, state updates will be batched by default.
ReactFlushSync:
ReactDOM.flushSync
takes a callback that updates React state. It invokes the callback, performs the update, and flushes them to DOM synchronously. Replacesunstable_batchedUpdates
.- When used in conjection with
React.startTransition
(see below) then the innermost one wins.
- When used in conjection with
ReactTransition:
React.startTransition
takes a callback that updates React state and updates it with lower priority (they call it non-urgent updates). See a real world example. See tests.setInputValue(input) // urgent (default priority) startTransition(() => { setSearchQuery(input) // non-urgent (idle priority) })
ReactHooks:
React.useDeferredValue
is (roughly) a sugar for this pattern (note: might be inaccurate):// Before const [searchQuery, setSearchQuery] = useState(input) useEffect(() => { startTransition(() => { setSearchQuery(input) }) }, [input]) // After const searchQuery = useDeferredValue(input)
ReactTransition:
React.useTransition
exposes anisPending
status for transitions that are started using the returnedstartTransition
callback. This allows the UI to display feedback to the user that the UI is still updating.const [isPending, startTransition] = useTransition() // Use the returned `startTransition` instead of React.startTransition.
ReactDOMUseId:
React.useId()
generates an ID that remains consistent between server and client rendering.ReactEmptyComponent: Components may now return
undefined
in addition tonull
.ReactDOMFizzServer: ^[“React Fizz” is the codename for new server-rendering architecture, which supports streaming and suspense.]
React.Suspense
can now be rendered on the server.- ReactDOMServerSuspense: When rendering synchronously (e.g.
ReactDOMServer.renderToString
) this will just render a fallback.
- ReactDOMServerSuspense: When rendering synchronously (e.g.
StrictEffectsMode: Under the New Root API, inside
<React.StrictMode>
,useEffect
anduseLayoutEffects
callbacks are now doubled in dev mode (see tests). That is, given the following code, it will callsubscribe(); unsubscribe(); subscribe()
:useEffect(() => { subscribe() return () => unsubscribe() }, [])
useSyncExternalStore:
state = useSyncExternalStore(subscribe, get)
is introduced to allow components “to safely and efficiently read from a mutable external source” in a way that works with concurrent rendering. For older React versions that support hooks,use-sync-external-store/shim
package can be used. See tests.ReactHooks:
useInsertionEffect
is for performing effects beforeuseLayoutEffect
, e.g. injecting style tags performantly in CSS-in-JS scenario when React is rendering concurrently.