React / React Transitions / Concurrent Transitions: React 18
Home /React /React Transitions /Concurrent Transitions: React 18

Concurrent Transitions: React 18

Concurrent Transitions: React 18

React 18 introduced concurrent rendering the ability to prepare multiple UI versions in parallel. The useTransition hook and startTransition function let you mark state updates as non-urgent, keeping the UI responsive during expensive re-renders.

Definition

A concurrent transition defers a state update to be processed at lower priority, allowing React to continue rendering the current UI and handling urgent updates (like typing) without interruption.

1. useTransition

Returns a boolean isPending flag and a startTransition function. Updates inside startTransition are treated as non-urgent.

SearchFilter.jsx
JSX (React 18)

import { useState, useTransition } from 'react';

export function SearchFilter({ items }) {
  const [query, setQuery]           = useState('');
  const [filtered, setFiltered]     = useState(items);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    const val = e.target.value;
    setQuery(val);                        // urgent — updates input immediately

    startTransition(() => {
      // non-urgent — expensive filter deferred
      setFiltered(items.filter(item =>
        item.toLowerCase().includes(val.toLowerCase())
      ));
    });
  }

  return (
    <div>
      <input
        value={query}
        onChange={handleChange}
        placeholder="Search…"
      />
      {isPending && <span>Updating…</span>}
      <ul>
        {filtered.map(item => <li key={item}>{item}</li>)}
      </ul>
    </div>
  );
}

2. useDeferredValue

Similar to useTransition but works when you don’t own the state update (e.g. a prop from a parent). Returns a deferred copy of the value that lags behind during transitions.

DeferredList.jsx
JSX (React 18)

import { useDeferredValue, memo } from 'react';

// Expensive child — memo prevents re-render unless deferredQuery changes
const SlowList = memo(({ query }) => {
  const items = heavyFilter(query);   // expensive operation
  return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>;
});

export function DeferredSearch({ query }) {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
      <SlowList query={deferredQuery} />
    </div>
  );
}

Note: Concurrent transitions are not visual animations, they are a scheduling mechanism. They prevent the UI from freezing during heavy state updates. Combine with visual transitions (opacity fade on isPending) to give users feedback.

DoFollow Links: