Concurrent Features

Back

Loading concept...

Concurrent React: The Magic of Doing Many Things at Once

The Traffic Controller Analogy

Imagine you’re a traffic controller at a busy intersection. Cars (tasks) are coming from all directions. Some cars are ambulances (urgent updates) that need to go through immediately. Other cars are delivery trucks (slower updates) that can wait a bit.

Concurrent React is like being a super-smart traffic controller. It helps React decide: “Which updates should go first? Which ones can wait?”


1. useId: Giving Everyone a Unique Name Tag

The Story

You’re organizing a school party. Every kid needs a name tag so teachers can find them easily. But here’s the tricky part—some kids signed up online (server), and some signed up at the door (client). You need to make sure no two kids get the same name tag!

What useId Does

useId creates a unique ID that stays the same whether your code runs on the server or in the browser.

import { useId } from 'react';

function EmailField() {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>Email:</label>
      <input id={id} type="email" />
    </div>
  );
}

Why It Matters for Accessibility

Screen readers (tools that help blind people use computers) need these IDs to connect labels to inputs. When a blind person clicks a label, the screen reader knows which input to focus on!

Before useId: You’d write id="email-1" and hope it didn’t clash with another id="email-1" somewhere else.

After useId: React guarantees uniqueness. Magic!


2. Server-Safe IDs: Same Name Tag, Two Worlds

The Story

Imagine you have a twin who lives in another city (the server). When someone asks your twin your name, and then asks you (on the client), you both need to say the exact same name. Otherwise, people get confused!

The Problem

Server says: "Your ID is abc123"
Browser says: "Your ID is xyz789"
React: "Wait, these don't match!" 💥

This causes a hydration mismatch—React gets confused when the server-rendered HTML doesn’t match what the browser creates.

The Solution

useId creates IDs using a special formula that works the same way on both server and client:

function Form() {
  const nameId = useId();
  const emailId = useId();

  // Both IDs will be identical on server AND client!
  return (
    <form>
      <label htmlFor={nameId}>Name</label>
      <input id={nameId} />

      <label htmlFor={emailId}>Email</label>
      <input id={emailId} />
    </form>
  );
}

Simple Rule: Never use Math.random() or Date.now() for IDs in React. They’ll be different on server vs client!


3. useTransition: The “Please Wait” Button

The Story

You’re at a restaurant. You order a fancy dish that takes 10 minutes to cook. Do you:

A) Stare at the kitchen door, unable to move? 😬 B) Sip your drink and chat while waiting? 😊

useTransition lets you pick option B!

What It Does

It marks some updates as “not urgent” so React can keep the app responsive while working on them.

import { useTransition, useState } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleSearch(text) {
    // This updates immediately (typing)
    setQuery(text);

    // This can wait (searching)
    startTransition(() => {
      const filtered = searchDatabase(text);
      setResults(filtered);
    });
  }

  return (
    <div>
      <input
        value={query}
        onChange={(e) => handleSearch(e.target.value)}
      />

      {isPending && <p>Searching...</p>}

      <ResultsList results={results} />
    </div>
  );
}

The Magic

  • Typing stays snappy: Your keystrokes appear instantly
  • Search happens in background: No freezing!
  • isPending tells you: “Hey, I’m still working on that slow stuff”

4. useDeferredValue: The Lazy Copy

The Story

You have a magic mirror that shows a slightly delayed reflection. When you move fast, the mirror shows where you were a moment ago. But when you stop, it catches up!

What It Does

useDeferredValue creates a delayed version of a value. React shows the old value while computing the new one.

import { useDeferredValue, useState, memo } from 'react';

function ProductList() {
  const [filter, setFilter] = useState('');
  const deferredFilter = useDeferredValue(filter);

  // Shows if we're showing stale data
  const isStale = filter !== deferredFilter;

  return (
    <div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter products..."
      />

      <div style={{ opacity: isStale ? 0.5 : 1 }}>
        <SlowProductGrid filter={deferredFilter} />
      </div>
    </div>
  );
}

useTransition vs useDeferredValue

useTransition useDeferredValue
You control when to update You control what gets delayed
Wraps the setState call Wraps the value itself
Use when you own the state Use when you receive props

5. startTransition: The Global Traffic Light

The Story

useTransition is like having a traffic light at your own intersection. But what if you need to control traffic from outside the car?

startTransition is the remote control version—you can use it anywhere, not just inside components!

When to Use It

import { startTransition } from 'react';

// In a regular function (not a component)
function handleTabClick(setTab, newTab) {
  startTransition(() => {
    setTab(newTab);
  });
}

// In an event handler
button.addEventListener('click', () => {
  startTransition(() => {
    updateBigList(newData);
  });
});

The Difference

// useTransition - inside components, gives you isPending
const [isPending, startTransition] = useTransition();

// startTransition - anywhere, no isPending
import { startTransition } from 'react';
startTransition(() => { /* ... */ });

Pro tip: If you need to show a loading spinner, use useTransition. If you just want the update to be interruptible, use plain startTransition.


6. Transition Priorities: Who Goes First?

The Story

In a hospital emergency room, patients aren’t seen in order of arrival. A heart attack (urgent) goes before a sprained ankle (can wait).

React works the same way with updates!

The Priority Ladder

graph TD A["Discrete Events"] --> B["Click, Type, Key Press"] B --> C["Highest Priority"] D["Continuous Events"] --> E["Scroll, Drag, Mouse Move"] E --> F["High Priority"] G["Default Updates"] --> H["Regular setState"] H --> I["Normal Priority"] J["Transitions"] --> K["startTransition wrapped"] K --> L["Low Priority - Can be interrupted!"]

How It Works

function App() {
  const [text, setText] = useState('');
  const [list, setList] = useState([]);

  function handleType(e) {
    // HIGH priority - happens immediately
    setText(e.target.value);

    // LOW priority - can wait
    startTransition(() => {
      setList(generateBigList(e.target.value));
    });
  }

  return (
    <div>
      <input value={text} onChange={handleType} />
      <BigList items={list} />
    </div>
  );
}

What happens when you type “abc” fast:

  1. “a” → text updates to “a”, list computation starts
  2. “b” → text updates to “ab”, list for “a” is interrupted, starts for “ab”
  3. “c” → text updates to “abc”, list for “ab” is interrupted, starts for “abc”
  4. You stop → list for “abc” finishes and displays

7. Suspense Internals: The Magic Behind the Curtain

The Story

Remember waiting for a video to buffer? You see a loading spinner, then suddenly—the video plays! Suspense is React’s way of showing loading spinners automatically.

How Suspense Works Inside

graph TD A["Component Renders"] --> B{Data Ready?} B -->|Yes| C["Show Component"] B -->|No| D["Throw Promise"] D --> E["Suspense Catches It"] E --> F["Show Fallback"] F --> G["Promise Resolves"] G --> A

The Three Players

1. Suspense Boundary - The catcher

<Suspense fallback={<Loading />}>
  <Comments />
</Suspense>

2. The Thrower - A component that “throws” a promise

function Comments() {
  // If data isn't ready, this "throws" a promise
  const comments = use(fetchComments());
  return <CommentList data={comments} />;
}

3. The Fallback - What shows while waiting

<div className="spinner">Loading comments...</div>

Suspense + Transitions = Best Friends

function App() {
  const [tab, setTab] = useState('home');
  const [isPending, startTransition] = useTransition();

  function switchTab(newTab) {
    startTransition(() => {
      setTab(newTab);
    });
  }

  return (
    <div>
      <Tabs current={tab} onChange={switchTab} />

      <div style={{ opacity: isPending ? 0.7 : 1 }}>
        <Suspense fallback={<Skeleton />}>
          {tab === 'home' && <Home />}
          {tab === 'profile' && <Profile />}
        </Suspense>
      </div>
    </div>
  );
}

Without transition: Click tab → instant blank → loading spinner → content

With transition: Click tab → old content stays (slightly faded) → new content appears


The Big Picture

graph TD A["User Action"] --> B{Urgent?} B -->|Yes| C["Update Immediately"] B -->|No| D["Wrap in Transition"] D --> E["Can Be Interrupted"] E --> F["Show Old While Computing New"] F --> G["User Stays Happy!"] C --> G

Quick Reference

Tool What It Does When to Use
useId Creates unique, server-safe IDs Forms, accessibility
useTransition Makes updates interruptible + gives isPending Heavy UI updates you control
useDeferredValue Creates a delayed copy of a value Expensive child components
startTransition Makes any update interruptible Outside components, libraries
Suspense Shows fallback while loading Data fetching, code splitting

You Did It!

You now understand how React juggles multiple tasks without dropping any! Think of these tools as your superpowers for building apps that feel fast, even when doing heavy work.

Remember: The user’s typing, clicking, and scrolling should NEVER freeze. Use transitions to keep everything smooth!

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.