Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

When a screen shows a completed change, it has usually only updated the client’s state. That is a prediction, not proof that the server accepted the request or that the database committed the change. A UI can show success while the request is still pending, after it has failed, or before a later read sees the same data. Treating those four states as one “saved” state is the source of most confusing reverts, stale views, and support tickets in interactive apps.

Four things that can all look like “saved”

Each layer in a typical web application has its own version of the data, and each one answers a different question. Keeping them separate is the most useful habit when debugging a mismatch.

Layer Question it answers What it does not prove
Rendered UI What the user currently sees Anything about the server or database
Request state Whether the call is pending, returned, or failed Whether the database transaction committed
Transaction outcome Whether the database committed the changes What a later read on another connection or cache returns
Subsequent read What a fresh query or refetch observes at that moment Whether the same read would return the same result a moment later

These states can diverge for ordinary reasons: the UI predicted success, the request failed, another user changed the same record, or a read used a different snapshot or cache than the write did.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why the UI changes before the save

Optimistic updates are a deliberate design. The interface applies the expected result immediately so that a click feels instant, then waits for the server to confirm it. React’s useOptimistic documentation describes this pattern directly: the optimistic state is shown immediately, and when the Action completes, React renders the base value again. If the base value has not been updated to match the successful change, the item will appear to revert even though the save succeeded.

A minimal example shows where the responsibility sits:

const [optimisticTodos, setOptimisticTodo] = useOptimistic(
  todos, // base state, owned by your data layer
  (current, doneId) =>
    current.map(t => (t.id === doneId ? { ...t, done: true } : t))
);

async function markDone(id) {
  startTransition(async () => {
    setOptimisticTodo(id);            // immediate, provisional
    await api.completeTodo(id);       // server action
    await refetchTodos();             // base state now matches the server
  });
}

The optimistic value is only a presentation layer. The base todos state, refetched or updated from the server response, is what the interface falls back to once the Action completes. Behaviour depends on the framework and version you use, so check the documentation for the exact API you have installed.

What a database commit actually means

In PostgreSQL, a commit is a separate event from the request that asked for it. The COMMIT statement commits the current transaction. According to the PostgreSQL 18 documentation, the changes then become visible to other transactions, and under the documented behaviour they are durable against a crash.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The transactions chapter of the same manual explains why an intermediate step is never a valid signal of success. Changes made inside an open transaction are not visible to other concurrent transactions, and the intermediate states between the steps are not visible to them either. The changes become visible together when the transaction completes. If the transaction is rolled back, or never reaches its commit, none of those changes exist for other sessions.

So a server handler can run several statements, see its own writes, and still have produced nothing for the rest of the system until the commit succeeds. An API that returns success before that commit, or a client that treats the handler’s internal progress as confirmation, is making a claim the database has not yet made.

Why a successful save can still look different later

Even after a commit, the application can see different data depending on how and when it reads. Three causes come up most often.

Read Committed does not freeze the world between statements

PostgreSQL’s default isolation level, Read Committed, is documented in the PostgreSQL 16 manual as allowing successive SELECT statements in one transaction to see different data when another transaction commits between them. A page that issues two queries can therefore show two different states of the same records. Stronger isolation levels exist for workloads that need a consistent snapshot across several statements, but they come with their own trade-offs and retry requirements.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Concurrent writers change the base state while you wait

Another user or background job can change the same list while an optimistic action is pending. If the optimistic update is written as a fixed change to a copy of the old list, it can overwrite or drop that other change when reconciled. React’s guidance for this case is to use a reducer-style update function that recalculates from the current base state, so the optimistic result is derived from whatever the server data is at that moment rather than from a stale snapshot.

Asynchronous commit trades durability for speed

PostgreSQL’s asynchronous-commit setting, controlled by synchronous_commit, can allow a transaction to report success before its WAL records are flushed to disk. The documentation identifies a crash window in which changes from such a transaction can be lost. Deployments that rely on the default durability guarantee should not assume that every configuration behaves identically. Check the value in the environment you actually run, not just the default you remember.

Failure paths and recovery

An optimistic update is a bet that the server will agree. The TanStack optimistic updates documentation (v3) states the risk plainly: when you optimistically update state before performing a mutation, there is a chance that the mutation will fail. Plan for that branch before shipping the feature.

A reliable flow follows these transitions explicitly:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. User action. The interface records the intent and applies the optimistic value, marked as pending if the distinction matters to the user.
  2. Request. The client sends the mutation. The server runs its handler inside a transaction.
  3. Transaction outcome. The database either commits all of the handler’s changes or none of them.
  4. Response. The client receives success or an error. A success response should describe only what the API actually guarantees.
  5. Reconciliation. On success, the client updates the base state or refetches authoritative data. On failure, it rolls back the optimistic value or refetches, and it tells the user what happened.
  6. Later read. A subsequent query returns what the database shows at that time, which may reflect other writers.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Diagnosing a mismatch

When the screen and the data disagree, work through the transitions in order rather than guessing.

  • The UI reverted after a request failed. This is expected if the optimistic value was never reconciled. Check whether the error path refetches or rolls back, and whether the error is shown to the user.
  • The UI shows a change, but a reload does not. The request may have failed silently, the success response may have been sent before the commit, or the reload read from a cache that was never invalidated.
  • The reload shows the change, but a colleague’s view does not. Check the isolation level, whether the other view is cached, and whether the other session’s read happened before the commit.
  • The change disappeared after a concurrent edit. The optimistic update was probably calculated from a stale copy of the list. Recalculate from the current base state.
  • The change disappeared after a database crash. Confirm the synchronous_commit value in use. If asynchronous commit was enabled, the crash window described in the documentation applies.

Wording that does not overclaim

User-facing copy should match the state the application can prove. “Saved” is accurate only after the server has confirmed the commit. Before then, use wording such as “Updating…” while the request is pending, and “Couldn’t save. Your change was reverted.” when it fails. A success message can say the change was saved only if the API response is defined as returned after commit. The front end cannot infer durability or replica freshness from its own state.

What the evidence does and does not establish

The behaviour described here comes from the official React, TanStack, and PostgreSQL documentation, using PostgreSQL manual versions 16 through 18. These sources establish how the components are documented to behave. They do not establish how any particular application handles API acknowledgments, caches, read replicas, retries, idempotency keys, or transaction boundaries, since those are implementation choices. No measured rate of optimistic-update failure or database inconsistency is established here, and none should be inferred from the examples above.

A reddit discussion in r/react shows how developers commonly phrase the question: one user described marking a todo complete, posting to a Node.js server, and updating the UI only after the database update succeeded. That is a single user’s wording, not a survey, but it matches the pattern of confusion this article addresses.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In practice, treat the UI as a prediction, treat the commit as the database’s answer, and treat each later read as an observation with its own timing. Keeping those three apart is what makes the gap between them manageable.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.