In March 2022, the React team announced the official release of React 18. This release came with a host of new features geared at performance improvement, based on the concept of “concurrent rendering”. The idea behind concurrent rendering is to make the process of rendering to the DOM interruptible.

Among the new features are five hooks: useId, useTransition, useDerredValue, useSyncExternalStore, and useInsertionEffect.

React useTransition Hook

By default, all React state updates are urgent. Different state updates in your application compete for the same resources, slowing it down. The useTransition React hook solves this problem by letting you mark some state updates as non-urgent. This allows urgent state updates to interrupt those with a lower priority.

The SearchPage Component

This simple program imitates a search engine that updates two states—an input field and some search results.

        import { useState } from "react";

function SearchPage() {
    const [input, setInput] = useState("")
    const [list, setList] = useState([]);
 
    const listSize = 30000
  
    function handleChange(e) {
        setInput(e.target.value);
        const listItems = [];
 
        for (let i = 0; i < listSize; i++){
            listItems.push(e.target.value);
        }
 
        setList(listItems);
    }
 
    return (
      <div>
          <label>Search the Web: </label>
          <input type="text" value={input} onChange={handleChange} />
 
          {list.map((item, index) => {
              return <div key={index}>{item}</div>
          })}
      </div>
    );
}

export default SearchPage;

The Updated App Component

        import SearchPage from "./Components/SearchPage";
 
function App() {
    return (
      <div>
          < SearchPage/>
      </div>
    );
}
 
export default App;

The code above renders a React application with an input field:

React search page

As you begin to type characters into the field, 30,000 copies of the typed text will appear below:

React populated search page

If you type several characters in quick succession, you should spot a delay. It affects the time that characters take to appear in both the input field and the “search result area”. This is because React is running both state updates at the same time.

If the demo runs too slowly or too quickly for you, try tweaking the listSize value accordingly.

Inserting the useTransition hook into the application will allow you to prioritize one state update over the other.

Using the useTransition Hook

        import {useState, useTransition} from "react";
 
function SearchPage() {
    const [isPending, startTransition] = useTransition();
    const [input, setInput] = useState("")
    const [list, setList] = useState([]);
 
    const listSize = 30000
 
    function handleChange(e) {
        setInput(e.target.value);

        startTransition(() => {
            const listItems = [];
 
            for (let i = 0; i < listSize; i++){
                listItems.push(e.target.value);
            }
 
            setList(listItems);
        });
    }
 
    return (
      <div>
          <label>Search the Web: </label>
          <input type="text" value={input} onChange={handleChange} />
 
          {isPending ? "...Loading results" : list.map((item, index) => {
              return <div key={index}>{item}</div>
          })}
      </div>
  );
}
 
export default SearchPage;

Updating your SearchPage component with the code above will prioritize the input field over the “search result area”. This simple change has a clear effect: you should start to see the text that you type into the input field immediately. Only the “search result area” will still have a slight delay. This is due to the startTransition application programming interface (API) from the useTransition hook.

The code that renders the search results to the UI is now using the startTransition API. This allows the input field to interrupt the search results’ state update. When the isPending() function displays “…Loading result” it indicates that a transition (from one state to the next) is happening.

React useDeferredValue Hook

The useDeferredValue hook allows you to defer the re-rendering of a non-urged state update. Like the useTransition hook, the useDeferredValue hook is a concurrency hook. The useDeferredValue hook allows a state to keep its original value while it is in transition.

The SearchPage Component With the useDeferredValue() Hook

        import { useState, useTransition, useDeferredValue } from "react";
 
function SearchPage() {
 
    const [,startTransition] = useTransition();
    const [input, setInput] = useState("")
    const [list, setList] = useState([]);
 
    const listSize = 30000
   
    function handleChange(e) {
        setInput(e.target.value);

        startTransition(() => {
            const listItems = [];
 
            for (let i = 0; i < listSize; i++){
                listItems.push(e.target.value);
            }
 
            setList(listItems);
        });
    }

    const deferredValue = useDeferredValue(input);

    return (
      <div>
          <label>Search the Web: </label>
          <input type="text" value={input} onChange={handleChange} />
 
          {list.map((item, index) => {
              return <div key={index} input={deferredValue} >{item}</div>
          })}
      </div>
    );
}
 
export default SearchPage;

In the code above you will see that the isPending() function no longer exists. This is because the deferredValue variable from the useDeferredValue hook replaces the isPending() function during state transition. Instead of the search results list refreshing when you type a new character, it will keep its old values until the application updates the state.

React useSyncExternalStore Hook

Unlike the useTransition and useDeferredValue hooks that work with application code, useSyncExternalStore works with libraries. It allows your React application to subscribe to and read data from external libraries. The useSyncExternalStore hook uses the following declaration:

        const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);
    

This signature contains the following:

  • state: the value of the data store that the useSyncExternalStore hook returns.
  • subscribe: registers a callback when the data store changes.
  • getSnapshot: returns the data store current value.
  • getServerSnapshot: returns the snapshot used during server rendering.

With the useSyncExternalStore, you can subscribe to an entire data store or a specific field within a data store.

React useInsertionEffect Hook

The useInsertionEffect hook is another new React hook that works with libraries. However, instead of data stores, the useInsertionEffect hook works with CSS-in-JS libraries. This hook addresses style rendering performance issues. It styles the DOM before reading the layout in the useLayoutEffect hook.

React useId Hook

You use the useId hook in situations that require unique IDs (except keys in a list). Its main purpose is to generate IDs that remain unique across client and server, avoiding the React server hydration mismatch error. The useId hook uses the following declaration:

        const id = useId()
    

In the declaration id is a unique string that includes the : token. After declaration, you can pass the id variable directly to the element(s) that need it.

What Value Does These New Hooks Add to React?

The useTransition and useDeferredValue hooks are application code hooks. Through concurrency rendering, they improve the performance of applications. The useId hook tackles the hydration mismatch error by creating unique IDs across client and server.

The useSyncExternalStore and useInsertionEffect hooks work with external libraries to facilitate concurrency rendering. The useInsertionEffect hook works with CSS-in-JS libraries. The useSyncExternalStore hook works with data store libraries like Redux store.

Together these hooks give a major boost to performance, which in turn improves user experience.