Skip to main content

Why can't you store promises in state?

What does "storing a promise in state" mean?

Example (the wrong way):

javascript
function User() { const [userPromise, setUserPromise] = useState(fetch('/api/user').then(r => r.json())); // trying to use it... userPromise.then(user => console.log(user)); return <div>User profile</div>; }

At first glance it looks fine - the promise is stored in state, then you can "wait" for the result. But in practice this is an anti-pattern and a source of many problems.


Why this is a bad idea

1. Promises aren't serializable or deterministic

React expects state (state) to be deterministic data that can be:

  • compared (Object.is(prev, next)),
  • reused across re-renders,
  • reset and restored (e.g. for time-travel debugging in Redux DevTools).

But a promise:

  • has no stable value (its result arrives later);
  • changes on its own (resolve/reject outside React);
  • cannot be serialized (cannot be saved to JSON or a snapshot).

React cannot tell exactly when it "changed", so storing a promise in state violates the very idea of reactivity.


2. A promise creates a side effect during render

javascript
const [data, setData] = useState(fetch('/api/user').then(r => r.json()));

Here fetch() runs on every render, and there can be several renders (React 18 double-renders in Strict Mode).

As a result:

  • new promises are created on every render;
  • React cannot stop them;
  • you get duplicate requests and leaks.

3. You can't safely use then inside a render

javascript
userPromise.then(data => setUser(data)); // a side effect in the component body

This breaks React's rule:

a render must be a pure function with no side effects.

An async operation inside the component body is a dirty render; React doesn't guarantee its order and can't correctly cancel or repeat it under Concurrent Rendering.


4. React doesn't know when the promise finished

React works on the principle:

"When state changes, the component needs to re-render."

But if you store the promise itself, not its result, React won't know when it resolves or rejects.

That means the UI will never update automatically:

javascript
const [userPromise, setUserPromise] = useState(fetch(...)); // even after the promise finishes, the component won't re-render

5. Race conditions and stale data

If the component unmounts or receives new props, the old promise can still finish and try to update state, triggering:

javascript
Warning: Can't perform a React state update on an unmounted component

The right way

Option 1. useEffect + state for the result

javascript
function User({ id }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { let active = true; setLoading(true); fetch(`/api/users/${id}`) .then(r => r.json()) .then(data => active && setUser(data)) .finally(() => active && setLoading(false)); return () => { active = false; }; // cleanup }, [id]); if (loading) return <p>Loading...</p>; return <p>{user.name}</p>; }

Here:

  • the promise is not stored in state;
  • we store the promise's result (user, loading, error);
  • React knows when to update the UI.

Option 2. Through React Query / SWR

Modern libraries take care of all this:

javascript
import { useQuery } from "@tanstack/react-query"; function User({ id }) { const { data, isLoading } = useQuery({ queryKey: ['user', id], queryFn: () => fetch(`/api/users/${id}`).then(r => r.json()) }); if (isLoading) return <p>Loading...</p>; return <p>{data.name}</p>; }

React Query:

  • does not store the promise in state;
  • caches results;
  • cancels stale requests;
  • triggers a re-render when the data is ready.

Option 3. React.Suspense (for React 18+)

If you use Suspense for data, React itself "waits" for the promise under the hood, but this is already a controlled mechanism, not a plain useState.

javascript
function User({ resource }) { const user = resource.user.read(); // may throw a promise return <p>{user.name}</p>; }

Here the promise is not manually stored in state, React manages it inside the Suspense mechanism.


Summary

Should not storeCan store
the promise itself (Promise)the promise's result (data, error, loading)
async effects during renderasync effects in useEffect
unstable objectsstable state values
side effects in the function bodya pure function + an effect

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.