Home / React / Lesson 10

Loading, error and empty UI

Intermediate 7 min read Lesson 10 of 11

A fetch is not instant. If you only have const [data, setData] = useState(null) and render data.title, the first paint crashes or shows nothing useful.

function Todo() {
  const [status, setStatus] = useState('loading'); // loading | error | ok
  const [todo, setTodo] = useState(null);
  const [message, setMessage] = useState('');

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then((r) => {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      })
      .then((json) => { setTodo(json); setStatus('ok'); })
      .catch((e) => { setMessage(e.message); setStatus('error'); });
  }, []);

  if (status === 'loading') return <p>Loading…</p>;
  if (status === 'error') return <p>Could not load: {message}</p>;
  return <p>{todo.title}</p>;
}

jsonplaceholder.typicode.com is a public demo API. Real apps need your own backend (see the Node course) and CORS configured on that server — the browser will block random APIs that do not send Access-Control-Allow-Origin.