Suggest an editImprove this articleRefine the answer for “Why can't you store promises in state?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)Storing the **promise itself** in `state` is an anti-pattern, because a promise isn't serializable, isn't deterministic, and changes on its own (resolve/reject) outside React, so React can't tell exactly when it "changed" and when the UI needs to update. **Key point:** instead of the promise itself, you should store its result (`data`, `error`, `loading`), and run the async call inside `useEffect`, not in the component body - otherwise the render stops being a pure function.Shown above the full answer for quick recall.Answer (EN)Image## 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 store | Can store | |---|---| | the promise itself (`Promise`) | the promise's result (`data`, `error`, `loading`) | | async effects during render | async effects in `useEffect` | | unstable objects | stable state values | | side effects in the function body | a pure function + an effect |For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.