Sending HTTP Requests (e.g. Connecting to a Database)Entry 5 of 7

Sending Data and Optimistic Updates

An optimistic update changes the UI immediately, before the server confirms, and rolls back if the request fails.

Notes

  • Sending data (POST, PUT, DELETE) happens in response to user actions, so it belongs in event handlers, not effects.
  • Optimistic flow:
    1. Save the current state.
    2. Update the UI as if the request succeeded.
    3. Send the request.
    4. On failure, restore the saved state and show an error.
  • It makes the app feel instant, but every optimistic update needs a rollback path.
  • Restoring a saved snapshot can undo other changes made while the request was in flight. For busy UIs, roll back only the affected item (e.g. filter it out by ID).
  • React 19 also has a useOptimistic hook, designed for form actions and transitions.

What / Why / When

What

Update first, confirm later.

Why

Better perceived performance; no spinner for actions that usually succeed.

When

Low-risk actions with a high success rate (likes, adding to a list, toggles).

Use case

Adding a place to the user's list and saving it with a PUT request.

Code compare

React
async function handleSelectPlace(selectedPlace: Place) {  if (userPlaces.some((place) => place.id === selectedPlace.id)) return;​  const previousPlaces = userPlaces; // 1. save  const updatedPlaces = [selectedPlace, ...userPlaces];  setUserPlaces(updatedPlaces); // 2. update the UI​  try {    await updateUserPlaces(updatedPlaces); // 3. send  } catch (err) {    setUserPlaces(previousPlaces); // 4. roll back    setErrorUpdatingPlaces(err instanceof Error ? err.message : "Failed to update places.");  }}

Targeted rollback, when adding a new item with a temporary ID:

React
async function addPlayer(newPlayer: { name: string }) {  const tempId = crypto.randomUUID();  setPlayers((prev) => [...prev, { ...newPlayer, id: tempId }]);​  try {    const res = await fetch("/api/players", {      method: "POST",      body: JSON.stringify(newPlayer),      headers: { "Content-Type": "application/json" },    });    if (!res.ok) throw new Error("Failed to save player.");  } catch (err) {    setPlayers((prev) => prev.filter((p) => p.id !== tempId)); // remove only this item    setError(err instanceof Error ? err.message : "Failed to save player.");  }}

The request and the optimistic logic both live in the service. The component only calls userPlacesService.addPlace(place) and reads userPlacesService.places().

Angular
import { Injectable, inject, signal } from "@angular/core";import { HttpClient } from "@angular/common/http";​@Injectable({ providedIn: "root" })export class UserPlacesService {  private http = inject(HttpClient);  private readonly _places = signal<Place[]>([]);  private readonly _error = signal<string | null>(null);​  readonly places = this._places.asReadonly();  readonly error = this._error.asReadonly();​  addPlace(place: Place) {    const previous = this._places(); // 1. save    if (previous.some((p) => p.id === place.id)) return;​    const updated = [place, ...previous];    this._places.set(updated); // 2. update the UI​    this.http.put("http://localhost:3000/user-places", updated).subscribe({      // 3. send      error: () => {        this._places.set(previous); // 4. roll back        this._error.set("Failed to update user data.");      },    });  }}

Differences and gotchas

  • The pattern is identical: save, update, send, roll back. React uses setState; Angular uses signal.set() or update().
  • If the server returns the saved item (with a real ID), replace the temporary item with it on success in both frameworks.

Quick reference

StepReactAngular
Update the UIsetX(...)signal.set() / update()
Sendawait fetch(...)http.put(...).subscribe()
Roll backsetX(previous) in catchsignal.set(previous) in error
Built-in helperuseOptimistic (forms/actions)—