Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
The Client Half  ›  Capstone Project

React Screens That Load Data

React 15 minA React screen that fetches from an API must handle loading, failed and ready states, starting from a value that means not loaded yet, and must treat its state as a copy of the server's data.

React Screens That Load Data

Sprout's dashboard is a React component that shows the user's plants. The plants live on the server, so the component starts with nothing and fills in a moment later, or fails to. Handling that gap well is what separates a solid screen from one that flashes blank, spins forever or crashes.

The rule

Every data screen has three states: loading, failed, ready.

import { useEffect, useState } from 'react';

export function PlantList() {
  const [plants, setPlants] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('/api/plants')
      .then((res) => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
      })
      .then(setPlants)
      .catch(setError);
  }, []);

  if (error) return <p>Could not load your plants.</p>;
  if (plants === null) return <p>Loading…</p>;
  if (plants.length === 0) return <p>No plants yet. Add your first one.</p>;
  return <ul>{plants.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}
  • useState(null): null means "not loaded yet", which is different from [], "loaded, and there are none". Two different situations deserve two different screens.
  • useEffect(..., []): runs the fetch after the component first appears. The empty array means it does not run again on later redraws.
  • The early returns handle each state before the happy path. An empty list is a special case of "ready" that deserves a friendly nudge.
  • key={p.id}: the database id is a stable key, so React can tell list items apart when they change.

A good failed state also offers a way out, such as a "Try again" button that runs the same fetch once more. A message with no next step leaves the user stuck.

One idea matters more than the syntax: the server is the source of truth, and React state is a copy. After any change, such as adding, watering or deleting a plant, either put the server's response into state or fetch the list again. Never let the copy drift from what the server said.

Data-fetching libraries such as TanStack Query and SWR wrap this pattern with caching and retries, but underneath they track the same three states.

The shortcut

Before writing the happy-path JSX, write the early returns first. If you cannot say what the screen shows while loading and after a failure, the screen is not designed yet.

The dashboard's first secondStep 1 of 4
  1. First render

    plants is null and error is null, so the user sees "Loading…".

Drills

Answer fast — trust the rule.

quick drill

Why does plants start as null instead of []?

quick drill

What does the empty array in useEffect(fn, []) mean here?

quick drill

The server replies 404 with the body {"error": "not found"}, and the code has no res.ok check. What happens?

quick drill

A POST that adds a plant succeeds. What keeps the dashboard honest?

Remember

  • Every data screen has three states: loading, failed, ready (and ready may be empty).
  • Shortcut: write the early returns before the happy path.
  • Trap: treating React state as the truth. It is a copy of the server's data; refresh it from the server's answers.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainOne state is not enough

A teammate's dashboard starts with useState([]), fetches /api/plants without checking res.ok, and renders the <ul> straight away. Explain what the user sees on a slow connection, on a brand-new account, and when the login has expired, and how the loading, failed and ready pattern fixes each.