FrontendPrep
Back to React Questions
reactMedium

React Concurrent Features: useTransition vs useDeferredValue

Understand the differences between useTransition and useDeferredValue hooks in React and when to use each to keep your UI responsive.

React Concurrent Features: useTransition vs useDeferredValue

In modern web applications, performing heavy rendering operations—such as filtering large lists or rendering complex charts—can block the browser's main thread. This causes the UI to freeze and makes the application feel unresponsive.

To solve this, React introduces Concurrent features that allow you to mark certain UI updates as low-priority. The two main hooks for this are useTransition and useDeferredValue.


The Core Concept: Transitions

React classifies updates into two categories:

  1. Urgent updates: Reflect direct physical interactions, like typing in an input, clicking a button, or selecting a dropdown.
  2. Transition updates: Transition the UI from one view to another (e.g., loading tab contents or filtering results). These can be delayed slightly without ruining the user experience.

By marking slow updates as transitions, React keeps the main thread free to handle urgent updates immediately.


1. What is useTransition?

The useTransition hook gives you access to a startTransition function and a pending state indicator (isPending). You wrap state-setting functions inside startTransition to tell React that this update can be interrupted if a more urgent event occurs.

Best Used For:

When you have direct control over the state setter function (e.g., setTab or setFilterQuery) and want to mark the state change itself as low priority.

Code Example:

import { useState, useTransition } from "react";
 
function TabContainer() {
  const [tab, setTab] = useState("home");
  const [isPending, startTransition] = useTransition();
 
  const handleTabChange = (nextTab: string) => {
    // Wrap the state setter in startTransition
    startTransition(() => {
      setTab(nextTab);
    });
  };
 
  return (
    <div>
      <button onClick={() => handleTabChange("home")}>Home</button>
      <button onClick={() => handleTabChange("about")}>About (Slow Render)</button>
      <button onClick={() => handleTabChange("contact")}>Contact</button>
 
      {isPending && <p>Loading new content...</p>}
      
      <div style={{ opacity: isPending ? 0.7 : 1 }}>
        {tab === "home" && <HomeView />}
        {tab === "about" && <SlowAboutView />}
        {tab === "contact" && <ContactView />}
      </div>
    </div>
  );
}

If a user clicks "About" (which triggers a slow render) and then immediately clicks "Contact", React halts the rendering of the "About" tab midway and renders the "Contact" tab instead. The UI never freezes.


2. What is useDeferredValue?

The useDeferredValue hook accepts a value (like a prop or state variable) and returns a deferred version of it. The deferred value lags behind the actual value, waiting until the main thread is idle before updating.

Best Used For:

When you receive a value from a parent component or another hook and do not have access to the state setter function itself. It is commonly used for search inputs.

Code Example:

import { useState, useDeferredValue, useMemo } from "react";
 
function SearchPage() {
  const [query, setQuery] = useState("");
  // query updates instantly on keystroke, deferredQuery lags behind
  const deferredQuery = useDeferredValue(query);
 
  return (
    <div>
      {/* Input is fast and responsive because it binds to the urgent 'query' state */}
      <input 
        value={query} 
        onChange={(e) => setQuery(e.target.value)} 
        placeholder="Search..." 
      />
      
      {/* The heavy list binds to 'deferredQuery' and renders only when idle */}
      <SlowList query={deferredQuery} />
    </div>
  );
}
 
// Wrap the slow list in React.memo so it only re-renders when deferredQuery changes
const SlowList = React.memo(({ query }: { query: string }) => {
  const items = useMemo(() => {
    // Heavy calculations/filtering
    return performHeavySearch(query);
  }, [query]);
 
  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
});

Here, typing remains perfectly smooth because query updates instantly. The list updates in the background using deferredQuery once typing slows down.


Comparison Summary

FeatureuseTransitionuseDeferredValue
How it worksWraps the state setting functionWraps the resulting state value or prop
Usage SyntaxstartTransition(() => setState(val))const deferredVal = useDeferredValue(val)
Pending StateProvides isPending booleanYou must manually check deferredVal !== val
ControlRequires access to the state setterWorks with any passed-down prop or value
Typical Use CaseTab switching, pagination navigationSearch input filtering, charts rendering

Key Takeaways

  • Both hooks optimize performance by allowing React to interrupt low-priority rendering in favor of user input.
  • Use useTransition when you control the state update function and want to wrap the updater.
  • Use useDeferredValue when you only receive the data value (e.g., as a prop in a child component) and cannot wrap the state update itself.
  • Always pair useDeferredValue with React.memo on the slow component to prevent it from re-rendering on every urgent state change.

Share this Resource

Help other developers level up by sharing this study guide.

More Technical Questions

Expand your mastery. Deep dive into other frontend interview challenges in this category.