Engrain by ByteRush

RCT.05.01 Mid-level· 12 minCore — asked often

Actions, useActionState and useOptimistic in React 19

React 19 Actions: async transitions with built-in pending state, form action functions, useActionState for results and errors, useFormStatus for nested buttons, and useOptimistic for instant UI that rolls back on failure.

Comes up in: React, Recruiter screen

Learn#

Nisha’s comment box waits a full second for the server before showing a new comment. Users click Post twice, and sometimes both get saved.

An optimistic update shows the result before the server confirms it. React 19’s useOptimistic keeps a temporary version of state that lasts only while an Action, an async transition, is running. When it ends, React shows the real state again. So a failed request rolls back by itself:

import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register(); // a DOM for Node; in a browser you already have one
const { createElement: h, useOptimistic, useState, startTransition } = await import("react");
const { createRoot } = await import("react-dom/client");

const wait = (ms) => new Promise((r) => setTimeout(r, ms));
async function saveComment(text) { // a fake server: rejects "spam"
  await wait(100);
  if (text === "spam") throw new Error("rejected");
  return text;
}
let send;
function Comments() {
  const [comments, setComments] = useState(["Nice photo!"]);
  const [optimistic, addOptimistic] = useOptimistic(comments, (list, text) => [...list, `${text} (sending…)`]);
  send = (text) => startTransition(async () => {
    addOptimistic(text); // shows at once
    try {
      const saved = await saveComment(text);
      startTransition(() => setComments((list) => [...list, saved]));
    } catch {
      // nothing to do: when the Action ends, the optimistic item disappears
    }
  });
  return h("ul", null, optimistic.map((c) => h("li", { key: c }, c)));
}
const box = document.createElement("div");
const root = createRoot(box);
root.render(h(Comments));
await wait(20);
const show = (label) => console.log(label.padEnd(18), [...box.querySelectorAll("li")].map((li) => li.textContent).join(" | "));
send("Love it"); await wait(20); show("while sending:");
await wait(150); show("after success:");
send("spam"); await wait(20); show("while sending:");
await wait(150); show("after failure:");
root.unmount();
This example uses Node.js, so it runs in our checks rather than in your browser.

Output (verified)

while sending:     Nice photo! | Love it (sending…)
after success:     Nice photo! | Love it
while sending:     Nice photo! | Love it | spam (sending…)
after failure:     Nice photo! | Love it
Optimistic add, success, then rollbackStep 1 of 4

Page

  • Nice photo!
  • Love it (sending…)

The user posts "Love it". Inside an Action, addOptimistic shows it at once with "(sending…)".

Read the steps as text
  1. The user posts "Love it". Inside an Action, addOptimistic shows it at once with "(sending…)".
  2. The server saves it. The real state gets the comment, and the Action ends: the optimistic copy is replaced.
  3. The user posts "spam". It also appears at once.
  4. The server rejects it. The real state never changed, so when the Action ends, the optimistic item disappears: an automatic rollback.

Predict#

Predict first

A form uses <form action={saveProfile}>. Where can a nested SubmitButton read whether the form is submitting, without props?

Interview angle#

Say it like this — about 30 seconds

“In React 19, an Action is an async function run in a transition. React tracks its pending state for you. A form can take a function as its action prop. The useActionState hook wraps an action. It gives back the last result and an isPending flag. The useFormStatus hook lets nested buttons read their form’s pending state. The useOptimistic hook shows a temporary value while the Action runs. If the request fails, the real state never changed. So the temporary value goes away when the Action ends.”

Stays on your device. Nothing is uploaded.
Follow-up What are Actions in React 19?
Async functions passed to startTransition or as a form action; React manages pending state, errors and optimistic updates while they run.
Follow-up How does useOptimistic work? What happens if the server request fails?
It shows a temporary state derived from real state while an Action runs; on failure the real state is unchanged, so the UI returns to it when the Action ends.
Follow-up What does useActionState return?
The latest state returned by the action, a wrapped action to pass to the form, and an isPending flag.

Practice#

Guided

Write the bookkeeping of an optimistic list: pending items, confirmations and rollbacks.

Done when

  • Pending items show with “(sending…)” after the base items.
  • confirm moves an item into the base list; fail removes it.
  • Several pending items keep their own order.
Your solution
Ctrl/⌘ + Enter

Remember#

Try to answer each card before you open it. Then add them to your reviews.

What is a React 19 Action?

An async function run in a transition, with pending state managed by React.

How long does a useOptimistic value last?

Until the Action that set it finishes.

Which hook gives a nested button its form's pending state?

useFormStatus.

What does useActionState return?

The action's last result, a wrapped action, and isPending.

Go deeper

Four layers. Stop at the one your interview needs; each ends with a quick check.

The core ideaEvery round
<form action={fn}> calls fn(formData) on submit, in a transition, and resets uncontrolled fields after success. No onSubmit, no preventDefault.
While it runs, the form is pending.
Check yourself: What does a form action function receive?

A FormData object with the form's fields.

How it works underneathSenior rounds
const [state, formAction, isPending] = useActionState(action, initial): the action receives the previous state and the form data, and returns the next state, such as { error: "Email taken" } Source: React: useActionState.
Check yourself: What signature does an action passed to useActionState have?

(previousState, formData) => newState (it may be async).

Edge cases and trapsWhere people slip
Form status comes from useFormStatus() in react-dom. It returns { pending, data, method, action } for the closest parent form.
After an await inside an Action, wrap state updates in another startTransition, as in the example.
Check yourself: Where must useFormStatus be called?

In a component rendered inside the form.

In productionStaff-level answers

useOptimistic rolls back automatically, but tell the user why it failed, with a toast or inline error Source: React: useOptimistic. Disable the submit button while pending, and send an idempotency key (WEB.15.01).

Check yourself: Why do optimistic updates still need server checks?

The server may reject the change; the UI must roll back and explain.

Finished the five steps? Mark it, and its cards join your reviews.

Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

Interview questions this answers

  • What are Actions in React 19?
  • How does useOptimistic work? What happens if the server request fails?