Use optimism when the next state is already known

React Router separates pending UI from optimistic UI for a useful reason. A pending state acknowledges an action while its result is unknown. An optimistic state goes further and renders the expected result before the action finishes. I only use the second when the submitted form data contains enough information to describe that result without guessing.

Toggling a saved item, renaming a label, or moving a task between known states can fit that rule. A payment, permission change, or destructive operation usually cannot. When success depends on facts the client does not have, an honest progress state is faster to trust than a confident fiction.

Keep confirmed state and submitted intent separate

A fetcher gives the interaction its own lifecycle and exposes the submitted FormData while the action runs. That creates three distinct facts: loader data is the last confirmed server state, fetcher.formData is the user's current intent, and action data is the server's response. Keeping those facts distinct makes the UI easier to reason about.

The optimistic value should be derived, not copied into a second client-side cache. After the action completes, React Router revalidates route data and the server becomes authoritative again. This avoids a shadow state that can drift from the database and turns rollback into a normal render rather than a special repair routine.

Failure must leave the user somewhere useful

Rollback is not just putting the old value back. The interface should explain why the change did not stick, keep recoverable input, and put the retry next to the failed action. A permission failure needs different language from a timeout, and a validation error should preserve the values that caused it.

I also decide what happens when two actions overlap. Disable a control when a duplicate would be harmful; allow concurrency when the operations are independent; attach errors to the exact item that failed. The useful test is not whether the happy path feels instant. It is whether the user can still explain the screen after the network disagrees.

Test the transition, not only the endpoints

The meaningful states are idle, submitting, revalidating, rejected, and settled. Tests should pause the action long enough to observe pending copy, submit twice where duplication matters, return a validation error, and confirm that server data replaces the optimistic value. Reduced-motion and keyboard users need the same status information without relying on animation.

Optimistic UI is a small distributed-system decision inside a component. Treating it that way keeps speed from becoming ambiguity.

References