We use cookies to enhance your experience on the site
CodeWorlds

Concurrent Mode and Suspense

Imagine a mission control center on a space station where hundreds of signals from different sensors are processed simultaneously. Some data is urgent (alarms), others less so (weather statistics from distant planets). React Concurrent Mode works on a similar principle - it allows you to prioritize interface updates so that the application remains responsive even during heavy operations.

What is Concurrent Mode?

Concurrent Mode is a set of mechanisms in React that allow interrupting and resuming rendering. Traditionally, React rendered synchronously - when it started an update, it had to finish it before it could react to anything else. In concurrent mode, React can:

  • Start rendering an update
  • Interrupt it when a more urgent update appears
  • Return to the interrupted work later

It is like a space controller who can set aside analyzing data from a distant probe when a sudden alert about an approaching asteroid appears.

startTransition - prioritizing updates

startTransition
is an API that tells React: "this update is not urgent, you can interrupt it." It is ideal for operations that can wait a moment, like filtering a large list or navigating between tabs.

1import { useState, startTransition } from 'react';
2
3function GalaxySearch() {
4  const [searchInput, setSearchInput] = useState('');
5  const [searchResults, setSearchResults] = useState([]);
6
7  const handleSearch = (value) => {
8    // Updating the input is URGENT - the user must see what they type
9    setSearchInput(value);
10
11    // Filtering results is LESS URGENT
12    startTransition(() => {
13      const filtered = galaxyDatabase.filter(
14        galaxy => galaxy.name.toLowerCase().includes(value.toLowerCase())
15      );
16      setSearchResults(filtered);
17    });
18  };
19
20  return (
21    <div>
22      <input
23        value={searchInput}
24        onChange={(e) => handleSearch(e.target.value)}
25        placeholder="Search galaxies..."
26      />
27      <GalaxyList results={searchResults} />
28    </div>
29  );
30}

Thanks to

startTransition
, the input responds immediately to typing, even if filtering thousands of galaxies takes a moment.

useTransition - tracking transition state

useTransition
is a hook that, in addition to
startTransition
, gives us an
isPending
flag indicating whether a transition is in progress:

1import { useState, useTransition } from 'react';
2
3function MissionDashboard() {
4  const [activeTab, setActiveTab] = useState('overview');
5  const [isPending, startTransition] = useTransition();
6
7  const switchTab = (tab) => {
8    startTransition(() => {
9      setActiveTab(tab);
10    });
11  };
12
13  return (
14    <div>
15      <nav>
16        {['overview', 'crew', 'systems', 'navigation'].map(tab => (
17          <button
18            key={tab}
19            onClick={() => switchTab(tab)}
20            style={{
21              opacity: isPending && activeTab !== tab ? 0.7 : 1,
22              fontWeight: activeTab === tab ? 'bold' : 'normal',
23            }}
24          >
25            {tab.toUpperCase()}
26          </button>
27        ))}
28      </nav>
29
30      {isPending && <div className="loading-bar">Loading panel...</div>}
31
32      <TabContent tab={activeTab} />
33    </div>
34  );
35}

Suspense - declarative waiting for data

Suspense
is a component that allows you to "suspend" rendering until data is ready. Instead of manually managing loading states, you declare what to show while waiting:

1import { Suspense } from 'react';
2
3function SpaceStation() {
4  return (
5    <div>
6      <h1>Space Station Alpha</h1>
7
8      <Suspense fallback={<LoadingSpinner message="Loading crew data..." />}>
9        <CrewList />
10      </Suspense>
11
12      <Suspense fallback={<LoadingSpinner message="Checking systems..." />}>
13        <SystemsStatus />
14      </Suspense>
15    </div>
16  );
17}

Each section loads independently - the user sees the crew data as soon as it is ready, without waiting for the systems status.

Suspense with lazy loading

The most common use of Suspense is loading components dynamically using

React.lazy
:

1import { lazy, Suspense } from 'react';
2
3// Dynamically loaded components
4const StarMap = lazy(() => import('./StarMap'));
5const MissionLog = lazy(() => import('./MissionLog'));
6const CrewManagement = lazy(() => import('./CrewManagement'));
7
8function CommandCenter() {
9  const [view, setView] = useState('map');
10
11  return (
12    <div>
13      <nav>
14        <button onClick={() => setView('map')}>Star Map</button>
15        <button onClick={() => setView('log')}>Mission Log</button>
16        <button onClick={() => setView('crew')}>Crew</button>
17      </nav>
18
19      <Suspense fallback={<div>Loading module...</div>}>
20        {view === 'map' && <StarMap />}
21        {view === 'log' && <MissionLog />}
22        {view === 'crew' && <CrewManagement />}
23      </Suspense>
24    </div>
25  );
26}

Nested Suspense

We can nest

Suspense
components, creating a loading hierarchy. The outer Suspense catches everything, while inner ones allow for more granular control:

1function MissionControl() {
2  return (
3    <Suspense fallback={<FullPageLoader />}>
4      <header>
5        <MissionTitle />
6      </header>
7
8      <main>
9        <Suspense fallback={<SectionLoader text="Map..." />}>
10          <InteractiveStarMap />
11        </Suspense>
12
13        <aside>
14          <Suspense fallback={<SectionLoader text="Statistics..." />}>
15            <MissionStats />
16          </Suspense>
17
18          <Suspense fallback={<SectionLoader text="Messages..." />}>
19            <CommunicationLog />
20          </Suspense>
21        </aside>
22      </main>
23    </Suspense>
24  );
25}

useDeferredValue - deferred values

useDeferredValue
is a hook that allows you to defer the update of a value. React first renders with the previous value (urgent update), then with the new one (less urgent):

1import { useState, useDeferredValue, useMemo } from 'react';
2
3function PlanetExplorer() {
4  const [filter, setFilter] = useState('');
5  const deferredFilter = useDeferredValue(filter);
6
7  // The list renders with the deferred filter value
8  const filteredPlanets = useMemo(() => {
9    return allPlanets.filter(p =>
10      p.name.toLowerCase().includes(deferredFilter.toLowerCase())
11    );
12  }, [deferredFilter]);
13
14  const isStale = filter !== deferredFilter;
15
16  return (
17    <div>
18      <input
19        value={filter}
20        onChange={(e) => setFilter(e.target.value)}
21        placeholder="Filter planets..."
22      />
23      <div style={{ opacity: isStale ? 0.6 : 1 }}>
24        {filteredPlanets.map(planet => (
25          <PlanetCard key={planet.id} planet={planet} />
26        ))}
27      </div>
28    </div>
29  );
30}

When to use each API?

| API | Use case | Example | |-----|----------|---------| |

startTransition
| Updates that can wait | Changing filters, navigating between tabs | |
useTransition
| Same as above + loading indicator | Switching views with loading state | |
Suspense
| Waiting for data or code | Lazy loading, data fetching | |
useDeferredValue
| Deferring value rendering | Filtering large lists, search |

Summary

Concurrent Mode and Suspense are like an advanced priority management system on a space station. Instead of processing all signals sequentially (blocking important alerts while analyzing less important data), React can intelligently prioritize updates:

  • startTransition allows marking less urgent updates
  • useTransition additionally reports the transition state
  • Suspense declaratively manages waiting for data
  • useDeferredValue defers expensive renders

These mechanisms work together, creating responsive interfaces even in complex applications with large amounts of data and heavy operations.

Go to CodeWorlds