useOptimistic: Temporary Feedback While a Save Is Pending
useOptimistic: Temporary Feedback While a Save Is Pending
A teacher marks attendance and expects quick feedback, but the real save may take time or fail. A temporary displayed change should not be confused with confirmed saved data.
useOptimistic displays a temporary optimistic value while an Action is pending. The underlying confirmed state remains the source of truth. An optimistic change anticipates success; it must reconcile with the actual result rather than promise success.
Understand
Separate confirmed and temporary values.
Build
Apply an optimistic update in an Action context.
Check
Describe failure and reconciliation honestly.
A temporary view follows an Action, not a confirmed save
Confirmed
Attendance begins absent.
Optimistic
Show present while the local operation is pending.
Reconcile
Commit confirmed success or return to the confirmed value.
Temporary state is not a second permanent truth
useOptimistic takes a base value and optionally an update function describing how optimistic input changes the displayed value. The Hook returns the optimistic value and an update dispatcher. The optimistic update must happen within an Action or Transition rather than being treated as an ordinary lasting setState.
When the Action completes, the optimistic value is reconciled against the current base state. If the confirmed value was updated to present, the displayed present value remains. If the operation failed and the base stayed absent, the temporary change does not become a confirmed fact. Explain what happened to users rather than silently implying persistence.
Mark attendance optimistically with a local success fixture
The local operation waits briefly and always succeeds. It saves nothing to a server. While pending, the optimistic view can say Present with a pending marker; after completion the confirmed state becomes present. React scheduling may make a short pending state difficult to observe, so the final result is the stable check.
React · JavaScript with JSX
import { useOptimistic, useState, useTransition, startTransition } from 'react';
export default function App() {
const [present, setPresent] = useState(false);
const [optimistic, setOptimistic] = useOptimistic(present);
const [pending, run] = useTransition();
function mark() {
run(async () => {
setOptimistic(true);
await new Promise(resolve => setTimeout(resolve, 60));
startTransition(() => setPresent(true));
});
}
return <main><h1>Local attendance preview</h1><p>{optimistic ? 'Present' : 'Absent'}{pending ? ' (pending)' : ''}</p>
<button disabled={pending || present} onClick={mark}>Mark present</button>
<p>Confirmed: {present ? 'present' : 'absent'}</p></main>;
}Expected screen and behavior
Mark present temporarily displays Present while pending. The local successful operation then makes Confirmed: present. No real attendance is stored.Follow temporary and confirmed values separately
React · Mark attendance optimistically with a local success fixture
import { useOptimistic, useState, useTransition, startTransition } from 'react';
export default function App() {
const [present, setPresent] = useState(false);
const [optimistic, setOptimistic] = useOptimistic(present);
const [pending, run] = useTransition();
function mark() {
run(async () => {
setOptimistic(true);
await new Promise(resolve => setTimeout(resolve, 60));
startTransition(() => setPresent(true));
});
}
return <main><h1>Local attendance preview</h1><p>{optimistic ? 'Present' : 'Absent'}{pending ? ' (pending)' : ''}</p>
<button disabled={pending || present} onClick={mark}>Mark present</button>
<p>Confirmed: {present ? 'present' : 'absent'}</p></main>;
}Initially the base is false, so both the optimistic view and the confirmed label say absent. The Action applies a temporary true value. The confirmed label still reads the separate base state until the successful fixture completes.
The post-await base update is explicitly wrapped in startTransition. After the Action settles, the optimistic display follows the updated base. This example follows the documented handling of post-await state updates; do not assume an async boundary automatically labels every later update as a Transition.
Choose whether optimism is suitable for the operation
An easily reversible attendance display may support optimistic feedback. A payment, irreversible permission change or action with a high likelihood of rejection may need a more cautious pending state. A fast interface is useful only if users can distinguish what is confirmed.
For lists, an optimistic temporary entry needs a stable temporary identity and later reconciliation with its confirmed server identity. Overlapping Actions and responses require the data layer’s supported ordering and conflict rules; a local success-only fixture does not implement that design.
Try the school task
React · starting example
import { useOptimistic, useState, useTransition, startTransition } from 'react';
export default function App() {
const [present, setPresent] = useState(false);
const [optimistic, setOptimistic] = useOptimistic(present);
const [pending, run] = useTransition();
function mark() {
run(async () => {
setOptimistic(true);
await new Promise(resolve => setTimeout(resolve, 60));
startTransition(() => setPresent(true));
});
}
return <main><h1>Local attendance preview</h1><p>{optimistic ? 'Present' : 'Absent'}{pending ? ' (pending)' : ''}</p>
<button disabled={pending || present} onClick={mark}>Mark present</button>
<p>Confirmed: {present ? 'present' : 'absent'}</p></main>;
}Make the local operation fail and preserve the confirmed absent value. Display a clear reason so the temporary present preview is not mistaken for success.
Worked answer
The Action catches the local rejection, leaves the base false and stores a failure message. When pending work ends, the optimistic display follows the unchanged base. This is a reproducible local failure, not a real server’s policy.
React · JavaScript with JSX
import { useOptimistic, useState, useTransition } from 'react';
export default function App() {
const [confirmed] = useState(false);
const [optimistic, setOptimistic] = useOptimistic(confirmed);
const [error, setError] = useState('');
const [pending, run] = useTransition();
function mark() {
run(async () => {
setOptimistic(true);
try { await new Promise((resolve, reject) => setTimeout(() => reject(new Error('Local save failed')), 60)); }
catch { setError('Not saved; attendance remains absent'); }
});
}
return <main><h1>Optimistic failure fixture</h1><p>{optimistic ? 'Present' : 'Absent'}</p>
<button disabled={pending} onClick={mark}>Try mark present</button><p role="alert">{error}</p></main>;
}Expected screen and behavior
After the local failure, Absent is restored and Not saved; attendance remains absent explains the result.Mistakes and boundaries
React 19 or later is required. useOptimistic is not persistent storage, request cancellation or server conflict resolution.
Handle expected failure with an appropriate result and UI. An optimistic indicator must not bypass authorization or claim that a transaction has committed.
Keep the optimistic update function pure if one is supplied. Do not send a network request from a reducer-like calculation used to derive the temporary view.
Check your reasoning
React · Mark attendance optimistically with a local success fixture
import { useOptimistic, useState, useTransition, startTransition } from 'react';
export default function App() {
const [present, setPresent] = useState(false);
const [optimistic, setOptimistic] = useOptimistic(present);
const [pending, run] = useTransition();
function mark() {
run(async () => {
setOptimistic(true);
await new Promise(resolve => setTimeout(resolve, 60));
startTransition(() => setPresent(true));
});
}
return <main><h1>Local attendance preview</h1><p>{optimistic ? 'Present' : 'Absent'}{pending ? ' (pending)' : ''}</p>
<button disabled={pending || present} onClick={mark}>Mark present</button>
<p>Confirmed: {present ? 'present' : 'absent'}</p></main>;
}Choose the answer that follows the data and the interaction, then read the explanation for each alternative.
Carry the idea forward
Use optimistic feedback only with clear reconciliation and failure behavior. Review Action scheduling and mutation and cache policies.
Read the primary reference: React: useOptimistic.
Reaching the recap and completing the quizzes are separate milestones.