React 19 Hooks: use(), useActionState & useOptimistic
React 19 ships a set of hooks built for the async, form-driven UIs you actually build. use() reads the value out of a promise (or context) and plugs into Suspense, so you stop hand-writing loading/error/data branches. useActionState manages a form action's returned state plus a pending flag in one hook. useFormStatus lets a child button know the surrounding form is submitting. And useOptimistic shows the expected result instantly, then reconciles with reality. Together they turn fiddly async glue into a few lines. By the end you'll know what each unwraps, when to reach for it, and how they pair with Server Actions.
Learn React 19 Hooks: use(), useActionState & useOptimistic in our free React course — a beginner-friendly interactive lesson with runnable examples, a…
Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
1️⃣ use(): Read Promises & Context
The use() hook unwraps a value. Pass it a promise and it returns the resolved data — suspending to the nearest while pending, and throwing to an error boundary if it rejects. The result: your component reads as if the data is simply there , with no loading/error branches in the body.
use() also reads context — and unlike every other hook, it can be called conditionally , inside an if or loop:
The demo models the three outcomes use() reacts to — pending suspends, fulfilled returns, rejected throws. Run it to lock in the mental model:
2️⃣ useActionState & useFormStatus: Forms Done Right
useActionState(action, initialState) returns [state, formAction, isPending] . Your action receives the previous state and the form data and returns the next state — perfect for validation errors or a success message. Wire formAction to the form and you get isPending for free.
When the submit button lives in its own reusable component, it can't see the parent's isPending . That's what useFormStatus is for — a child inside the reads the surrounding form's status directly:
Now model the heart of useActionState : the action function that takes the previous state plus form data and returns the next state. Fill in the blanks:
3️⃣ useOptimistic: Instant, Reconciled UI
Network calls take time, but users want feedback now . useOptimistic(state, updateFn) returns an optimisticState and an addOptimistic function. You show the expected result immediately, run the real action, and when it finishes React automatically drops the optimistic value and re-renders from the true state — so a failure just snaps back, no manual undo.
The demo models the show-then-reconcile timeline: the UI jumps to the optimistic value, then settles on the server's confirmed value. Run it:
Each scenario needs exactly one of these React 19 hooks. Finish pickHook so it returns the right name for each case — the decision you make in real code.
These lines of an optimistic like handler are scrambled. Put them in the correct order:
1) Why must the promise passed to use() be created outside render?
Creating it in the body makes a new promise every render, which Suspense can never settle — an infinite loading loop. Create it outside render (prop, cache, or a Server Component) and pass the stable promise in.
2) What three things does useActionState return?
[state, formAction, isPending] — the latest state your action returned, the action to put on , and a pending flag while it runs.
3) Your reusable needs to know the form is submitting. Which hook?
useFormStatus (from react-dom ). A child of the form reads {' '} directly — no prop drilling.
4) With useOptimistic , what happens if the real action fails?
React discards the optimistic value and re-renders from the real state — so the UI snaps back automatically. You never write manual undo logic.
📋 Quick Reference
Finish signupAction so it returns an error state for bad input and a message state on success — exactly the shape useActionState threads back into your form.
Practice quiz
What does the use() hook do with a promise?
- Starts a new fetch
- Caches the result forever
- Reads (unwraps) the promise's value, suspending while pending
- Converts it to state
Answer: Reads (unwraps) the promise's value, suspending while pending. use() unwraps a promise: it suspends to the nearest <Suspense> while pending and returns the resolved value.
Why must the promise passed to use() be created outside render?
- Creating it in the body makes a new promise every render, which Suspense can never settle
- It runs faster
- Promises can't be created in components
- To avoid TypeScript errors
Answer: Creating it in the body makes a new promise every render, which Suspense can never settle. A new promise per render causes an infinite loading loop; create it outside render and pass a stable one.
Unlike other hooks, use() may be called…
- Only at the top level
- Only in class components
- Only once per app
- Conditionally, inside an if or a loop
Answer: Conditionally, inside an if or a loop. use() is unusual: it's allowed inside conditionals and loops, unlike the rules-of-hooks for other hooks.
What does useActionState return?
useActionState(action, initialState) returns [state, formAction, isPending].
A useActionState action function receives…
- The previous state and the form data, and returns the next state
- Only the form data
- Just the event object
- The component's props
Answer: The previous state and the form data, and returns the next state. The action gets (prevState, formData) and returns the next state — ideal for validation errors or a success message.
Which hook lets a reusable child SubmitButton know the form is submitting?
- useActionState
- useOptimistic
- useFormStatus
- useState
Answer: useFormStatus. useFormStatus, read by a child inside the <form>, exposes { pending } without prop drilling.
useFormStatus is imported from…
- react
- react-dom
- react-router-dom
- @reduxjs/toolkit
Answer: react-dom. useFormStatus comes from react-dom and only works inside a <form>.
With useOptimistic, what happens if the real action fails?
- You must manually undo the change
- The UI stays on the optimistic value
- It throws to an error boundary
- React discards the optimistic value and re-renders from the real state
Answer: React discards the optimistic value and re-renders from the real state. The optimistic value is disposable; React drops it and shows the true state, so the UI snaps back automatically.
useFormStatus must be read…
- In the same component that renders the <form>
- In a child component inside the <form>
- At the app root
- Inside useEffect
Answer: In a child component inside the <form>. It reads the nearest enclosing form, so it belongs in a child, not the component that renders the form.
Around a component that calls use() on a promise, you should wrap…
- Nothing extra is needed
- A Redux Provider
- A <Suspense> boundary and an error boundary
- A useMemo
Answer: A <Suspense> boundary and an error boundary. Suspense catches the pending state and an error boundary catches rejection; both are needed.