useSyncExternalStore: Demystified for Practical React Development
Stop UI tearing in React! useSyncExternalStore safely syncs external data. Get best practices for this advanced hook (like stable functions) at EpicReact.dev.

Fetching data in React is easyβhandling the user experience while waiting for that data is not. Spinners, loading states, and error messages often clutter our components and make code harder to maintain. Wouldn't it be great if React could handle async UI declaratively, just like it does with everything else?
That's exactly what React Suspense is designed to do. Let's look under the hood at how it works, why it's so clever, and how you can use it to simplify your async UI.
React Suspense is a mechanism that lets you declaratively specify loading and
error states for components that depend on asynchronous data. Instead of
manually tracking loading and error state, you wrap your component tree in
<Suspense>
and let React handle the rest.
Here's the wild part: Suspense works by catching thrown promises π±. When a
component needs data that isn't ready, it throws a promise. React catches that
promise, pauses rendering, and shows the fallback UI you provided to
<Suspense>
. When the promise resolves, React tries rendering again.
This is possible because in JavaScript, you can synchronously stop a function by throwing. React leverages this to "pause" rendering until the data is ready.
use
Hook Works with SuspenseThe use
hook in React 19+ is the key to this pattern. Instead of using
await
, you pass a promise to use
, and it either returns the resolved value
(if ready) or throws the promise (if not):
function PhoneDetails() { const details = use(phoneDetailsPromise) // details is ready here!}
But where does phoneDetailsPromise
come from? You should trigger the fetch
outside the render, so you don't start a new request on every render. For
example:
// this could be in an event handler or something, just not within the body of// a client component (server components can't use `use` and can just use// `fetch` directly).const phoneDetailsPromise = fetch('/api/phone-details').then((res) => res.json(),)
If the promise isn't resolved, use
throws it, triggering Suspense. If it's
resolved, you get the value.
If the promise rejects, React will look for an Error Boundary and render its fallback. This lets you handle errors declaratively, just like loading states:
import { Suspense } from 'react'import { ErrorBoundary } from 'react-error-boundary'function App() { return ( <ErrorBoundary fallback={<div>Oh no, something bad happened</div>}> <Suspense fallback={<div>Loading phone details...</div>}> <PhoneDetails /> </Suspense> </ErrorBoundary> )}
Let's build a simple Suspense data fetcher from scratch:
let userPromisefunction fetchUser() { userPromise = userPromise ?? fetch('/api/user').then((res) => res.json()) return userPromise}function UserInfo() { const user = use(fetchUser()) return <div>Hello, {user.name}!</div>}function App() { return ( <Suspense fallback={<div>Loading user...</div>}> <UserInfo /> </Suspense> )}
Notice how there's no manual loading or error state in UserInfo
. Suspense and
Error Boundaries handle it all.
You could imagine how to expand this example to be more complex (parameterized,
cached, etc.), and I'll invite you to join me in the
Epic React Suspense Workshop
to dive deeper into all of that π. In fact, we build our own implementation of the use
hook from scratch so you really understand what's going on under the hood.
This pattern is powerful because it is:
React Suspense is great for async UI. By "throwing promises" and using the use
hook, you can write cleaner, more declarative components that handle loading and
error states automatically. Try it out in your next project and see how much
simpler your code can be!
Delivered straight to your inbox.
Stop UI tearing in React! useSyncExternalStore safely syncs external data. Get best practices for this advanced hook (like stable functions) at EpicReact.dev.
Why can't React just magically know what to do without a key?
Simplify and speed up your app development using React composition
Speed up your app's loading of code/data/assets with "render as you fetch" with and without React Suspense for Data Fetching