Skip to content

Intermediate · Connecting frontend and backend · topic 20 of 34 · 30 min

Keeping the screen in sync

Explanation

After you create, edit or delete a note, the list on screen must match the server. You can refetch the list, update it from the server's answer, or update it optimistically: change the screen at once and roll back if the server fails. Libraries like TanStack Query handle caching and refetching for you. Also ignore late answers to old searches, and wait for a pause in typing before searching.

The screen can go out of date

The notes list in React state is a copy of what the server has. When the user creates, edits or deletes a note, the server changes, but the copy does not change by itself. You have three ways to bring them back in line.

WayHowGoodNot so good
RefetchAfter the change, GET /api/notes againSimple and always correctOne more request; a short wait
Update from the answerPut the note the server returned into the local listNo extra requestYou must write the add, edit and delete logic
Optimistic updateChange the screen first, then send the request; undo if it failsFeels instant, even on slow networksNeeds rollback code; wrong for risky actions
javascript
// after POST answered 201 with the new note
setNotes((list) => [created, ...list]);
// after PATCH answered 200 with the updated note
setNotes((list) => list.map((n) => (n.id === updated.id ? updated : n)));
// after DELETE answered 204
setNotes((list) => list.filter((n) => n.id !== id));
Updating the local list from the server's answer. Each line makes a NEW array; React re-renders only when state is replaced, not changed in place.

Optimistic updates

An optimistic update assumes the request will work. When the user taps the pin icon, the note moves to the top at once; the PATCH request goes in the background. If the server fails, you roll back: put the old list back and show a short message.

Think of it like this: At a busy kirana shop, the shopkeeper hands you the packet of biscuits while your UPI payment is still processing. Nearly every payment succeeds, so nobody waits. If the payment fails, you hand the packet back. That is an optimistic update with a rollback.
javascript
async function togglePin(id) {
  const before = notes; // keep the old list for rollback
  const target = notes.find((n) => n.id === id);
  setNotes(notes.map((n) => (n.id === id ? { ...n, pinned: !n.pinned } : n)));
  try {
    await api('/notes/' + id, { method: 'PATCH', body: { pinned: !target.pinned } });
  } catch (err) {
    setNotes(before); // roll back
    showToast('Could not pin the note. Please try again.');
  }
}
Optimistic pin with rollback. The screen changes before the server answers.
Careful: Use optimistic updates only for small actions that almost always work and are easy to undo: pin, like, mark as done. Never show a payment, an order or a signup as successful before the server confirms it, and always tell the user when you roll back.

Server-state libraries

Server state is data that really lives on the server, which the screen only shows a copy of. Libraries such as TanStack Query (also called React Query) and SWR manage that copy for you: they cache answers by a key like ["notes"], show cached data at once while fetching fresh data in the background, refetch when you come back to the tab, retry failed reads, and let you invalidate (mark as old) a key after a change so it is fetched again.

javascript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function NotesPage() {
  const queryClient = useQueryClient();
  const { data, isPending, error } = useQuery({
    queryKey: ['notes'],
    queryFn: () => api('/notes'),
  });
  const createNote = useMutation({
    mutationFn: (input) => api('/notes', { method: 'POST', body: input }),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['notes'] }),
  });
  // createNote.mutate({ title: 'New idea' })
}
TanStack Query: loading, error, caching and refetch after a change, in a few lines. It still uses your api() client inside.

Race conditions in search

A race condition is a bug where the result depends on which of two things finishes first. The user types "ph", then "phy". Two searches go out. If the "ph" answer is slower and arrives last, the screen shows results for "ph" while the box says "phy". The fix: give each request a number and ignore any answer that is not from the latest one, or cancel the older request with an AbortController.

javascript
let latest = 0;
async function search(text) {
  const id = ++latest; // this request's number
  const data = await api('/notes?q=' + encodeURIComponent(text));
  if (id !== latest) return; // a newer search started: ignore this old answer
  setNotes(data.notes);
}
A latest-request guard. encodeURIComponent makes spaces and symbols safe inside a URL.

Debounce typing

Typing "physics" is seven key presses. Sending seven searches wastes data and server time. Debounce means waiting until the user stops typing for a short pause, about 300 ms, and only then sending one request.

javascript
function debounce(fn, ms) {
  let timer;
  return (...args) => {
    clearTimeout(timer); // a new key press cancels the waiting call
    timer = setTimeout(() => fn(...args), ms);
  };
}

const debouncedSearch = debounce(search, 300);
// <input onChange={(e) => debouncedSearch(e.target.value)} />
Each key press restarts the 300 ms timer, so only the last one sends a request.
Tip: Use both: debounce cuts the number of requests, and the latest-request guard keeps the answers in the right order. TanStack Query also avoids the race, because each search text is its own query key.

Watch the lesson

Keeping the screen in sync · 1/7

Server changed Screen copy must follow

When a note is created, edited or deleted, the list on screen must match the server again.

Prefer a video by a teacher? Find free videos on “react optimistic updates tanstack query invalidate race condition debounce search” on YouTube ↗

In real life: Liking a post in a social app

In most social and chat apps, when you tap the heart on a post it turns red at once and the count goes up, even on a slow 4G connection. The request goes in the background. If it fails, the heart quietly turns grey again. The app search box works the same careful way: it waits until you stop typing, and it ignores answers to words you have already changed. A food delivery app, on the other hand, never shows "Order placed" until the server confirms, because a false success there costs real money.

Practice (0/3 done)

Task 1 · Code · Javascript

Write updateList(notes, action) that returns a NEW notes array after a server answer. action is one of: { type: "created", note } (put the note at the start), { type: "updated", note } (replace the note with the same id), { type: "deleted", id } (remove the note with that id). For any other type, return a copy of the list. Never change the input array.

Task 2 · Code · Javascript

Write async togglePinOptimistic(notes, id, saveFn, show). 1) Build a NEW list where the note with that id has pinned flipped (true to false or false to true); other notes stay the same. 2) Call show(newList) at once, BEFORE waiting for the server. 3) await saveFn(id, newPinnedValue). 4) If it works, return the new list. If saveFn throws, roll back: call show(notes) with the original list and return the original list. Do not change the input array or its notes.

Task 3 · Code · Javascript

Write createLatestSearch(searchFn, show). It returns an async function search(text). Each call to search awaits searchFn(text) (which returns a Promise of results) and then calls show(results), but ONLY if no newer search was started in the meantime. Answers to older searches that arrive late must be ignored. Tip: keep a counter outside search and give each call its own number.

Quick quiz

Question 1

1.What is an optimistic update?

Question 2

2.Which action should NOT be shown optimistically?

Question 3

3.The user types "ph" then "phy", and the "ph" answer arrives last. What prevents wrong results on screen?

Question 4

4.In TanStack Query, what do you usually do after a successful create so the list is fresh?

Question 5

5.What does debouncing the search box do?

Read more (free)

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

← Previous: Loading, errors and slow networks