Back to all notes

useDeferredValue — Keep Input Snappy During Slow Renders

Defer updating slow results so typing stays responsive.

2 min read

Introduction

useDeferredValue lets React treat some updates as lower priority. You can keep user input instant while deferring slow, derived renders—like big filtered lists—until the browser has time.

Why this matters

Some renders are slow—big lists, heavy formatting. useDeferredValue lets React prioritize urgent updates (like typing) and defer lower‑priority work (like rendering the slow list).

The problem

Passing the raw query to an expensive component forces it to re-render on every keypress.

Inefficient approach

UI stutters because the slow list renders on each keystroke:

import { useState } from "react";

function SlowList({ query }) {
  const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
  const filtered = items.filter(item => item.toLowerCase().includes(query.toLowerCase()));
  
  return (
    <ul>
      {filtered.slice(0, 100).map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

function App() {
  const [query, setQuery] = useState("");

  return (
    <div>
      <h1>useDeferredValue</h1>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <SlowList query={query} />
    </div>
  );
}

export default App;

The solution

Defer the query value passed to an expensive component.

import { useState, useDeferredValue } from "react";

function SlowList({ query }) {
  const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
  const filtered = items.filter(item => item.toLowerCase().includes(query.toLowerCase()));
  
  return (
    <ul>
      {filtered.slice(0, 100).map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

function App() {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);

  return (
    <div>
      <h1>useDeferredValue</h1>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <SlowList query={deferredQuery} />
    </div>
  );
}

export default App;

Step-by-step

  1. Keep the controlled input bound to query.

  2. Create deferredQuery = useDeferredValue(query).

  3. Pass deferredQuery down to the slow component.

  4. Memoize the slow component where appropriate to avoid unnecessary work.

Tips

  • Combine with memo for child components that render based on the deferred value.

  • Don’t defer critical UI (like validation errors that must be immediate).

  • Measure before and after—defer only what’s truly slow.

Knowledge base

Problem snippet:

<SlowList query={query} />

Solution snippet:

const deferredQuery = useDeferredValue(query);
<SlowList query={deferredQuery} />