notes.dt.in.th

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.

  1. ReactDOMRoot: ReactDOM.createRoot supersedes ReactDOM.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), unless ReactDOM.flushSync is used.
  2. ReactDOMRoot: ReactDOM.hydrateRoot supersedes ReactDOM.hydrate.

    // Before
    ReactDOM.hydrate(<App tab="home" />, container)
    
    // After
    const root = ReactDOM.hydrateRoot(container, <App tab="home" />)
  3. ReactDOMFiberAsync: In the New root API, state updates will be batched by default.

  4. ReactFlushSync: ReactDOM.flushSync takes a callback that updates React state. It invokes the callback, performs the update, and flushes them to DOM synchronously. Replaces unstable_batchedUpdates.

  5. 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)
    })
  6. ReactTransition: React.useTransition exposes an isPending status for transitions that are started using the returned startTransition 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.
  7. ReactDOMUseId: React.useId() generates an ID that remains consistent between server and client rendering.

  8. ReactEmptyComponent: Components may now return undefined in addition to null.

  9. 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.

  10. StrictEffectsMode: Under the New Root API, inside <React.StrictMode>, useEffect and useLayoutEffects callbacks are now doubled in dev mode (see tests). That is, given the following code, it will call subscribe(); unsubscribe(); subscribe():

    useEffect(() => {
      subscribe()
      return () => unsubscribe()
    }, [])
  11. 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.

  12. ReactHooks: useInsertionEffect is for performing effects before useLayoutEffect, e.g. injecting style tags performantly in CSS-in-JS scenario when React is rendering concurrently.