useTransition() and useDeferredValue() hooks in React

useTransition() hook

This hook is a way to tell React that some state updates have lower priority comparing to regular state updates.

const [isPending, startTransition] = useTransition();

As you can see when calling useTransition() we’re getting 2 elements:

  1. Boolean value indicating if the low priority update is in progress
  2. Function you should use to wrap low priority update

Say we have a huge list of items and a search field. When user enters search query we filter out the items satisfying criteria. In this case we can mark search query update as a low priority update. Here is how it works:

const Dashboard = ({ items }) => {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState("");

  const handleQueryChange = (event) => {
    startTransition(() => {
      setQuery(event.target.value);
    });
  };

  return (
    <div>
      <input type="text" onChange={handleQueryChange} />
      <ItemList items={items} query={query} />
    </div>
  );
};

This approach allows us to schedule updates to qeury without degrading UX for user typing into the search field. This ensures that field remains responsive to user keystrokes.

It’s worth mentioning that we could rewrite above handler with using setTimeout():

const handleQueryChange = (event) => {
  setTimeout(() => {
    setQuery(event.target.value);
  }, 0);
};

In this case the function passed to setTimeout() will run asyncronously comparing to the function passed into startTransition() which runs syncronously. When using startTransition React knows about updates in transition and decides how and when to render the update.

useDeferredValue() hook

Overall it does the same thing as useTransition() and was intended to improve the responsiveness of laggy UIs. useTransition() works when you have the access to set function updating state, but in case the value is coming in props you should rely on useDeferredValue() to achieve the same result.

import { useDeferredValue } from "react";

const ItemList = ({ items, query }) => {
  const deferredQuery = useDeferredValue(query);
  const filteredItems = filterItems(items, deferredQuery);

  return (
    <ul>
      {filteredItems.map((item) => (
        <li key={item.id}>{item.title}</li>
      ))}
    </ul>
  );
};

In the above case React will prioritize UI updates over items filtering.

Caveats

  • You should not use both hooks at the same time as they are designed to accomplish the same thing.
  • The function you pass to startTransition must be synchronous. If you pass asyncronous function and try to perform state updates from there they wouldn’t be marked as transitions by React. So they will work as plain state updates.
  • A state update marked as a transition will be interrupted by other state updates.
  • React can batch multiple transitions together.

References

  1. useDeferredValue
  2. startTransition