How to Update React Suspense in React
React Suspense is a powerful feature that coordinates the loading states of your component tree, but updating suspended content without causing jarring UI layout shifts requires a specific approach. This article covers how to manage, trigger, and smoothly update React Suspense boundaries using modern React features like transitions, state updates, and cache invalidation.
Understanding How Suspense Updates Work
React Suspense does not have a direct “update” function. Instead, a Suspense boundary updates its visual state automatically based on the promises thrown by its child components.
When a child component requests new data (such as during a route
change or search query), it “suspends” the render. React catches this,
pauses rendering the children, and displays the fallback
UI. Once the promise resolves with the updated data, React renders the
child components with the new state.
To control this process smoothly and prevent the UI from flickering back to a loading spinner during updates, React provides specific hooks and patterns.
1. Preventing Fallbacks
with useTransition
The most common issue when updating Suspense-enabled data is the
sudden reappearance of the loading spinner. To update data while keeping
the existing UI visible until the new data is ready, you should use the
useTransition hook.
The useTransition hook marks a state update as a
non-urgent transition. React will keep the old UI fully interactive
while fetching the new data in the background.
import { useState, useTransition, Suspense } from 'react';
function SearchContainer() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleSearch(nextQuery) {
// Wrap the state update in startTransition
startTransition(() => {
setQuery(nextQuery);
});
}
return (
<div>
<SearchInput onChange={handleSearch} />
<div style={{ opacity: isPending ? 0.8 : 1 }}>
<Suspense fallback={<Spinner />}>
<SearchResults query={query} />
</Suspense>
</div>
</div>
);
}By wrapping setQuery in startTransition,
React delays updating the Suspense boundary’s fallback. The user sees
the old results (optionally styled with lower opacity to indicate a
pending state) until the new search results are fully loaded.
2. Updating Suspense via Cache Invalidation
Because Suspense relies on a data-fetching cache, updating the data inside Suspense requires updating the cache container.
If you are using data-fetching libraries like TanStack Query (React Query) or SWR, you update Suspense by invalidating the query cache or triggering a refetch:
import { useQueryClient } from '@tanstack/react-query';
function UpdateButton() {
const queryClient = useQueryClient();
function handleUpdate() {
// Invalidating the query triggers the Suspense boundary to fetch updated data
queryClient.invalidateQueries({ queryKey: ['userData'] });
}
return <button onClick={handleUpdate}>Refresh Data</button>;
}When the cache is invalidated, the query client throws a new promise, putting the Suspense boundary back into a loading or transition state while the updated data is fetched.
3. Forcing Updates with the
key Prop
If you need to completely reset a Suspense boundary and force it to
show its fallback spinner during an update, you can change the
key prop on the Suspense component or its
immediate child.
Changing the key forces React to unmount the old
component tree, discard its state, and mount a brand new instance.
// Changing the userId key forces Suspense to show the fallback during the update
<Suspense key={userId} fallback={<ProfileSkeleton />}>
<ProfileDetails id={userId} />
</Suspense>Use this strategy when switching between entirely different logical entities (like switching from User A to User B) where showing old cached data would be confusing to the user.