← Back to React Course | Chapter 10: Data Fetching & Async | Lesson 4 of 10

Loading and Error States

Loading and error states are like traffic lights for your data — green means it's ready, yellow means wait, and red means something went wrong.

The Three States of a Data Request

Any data-fetching UI generally needs to represent three distinct situations: still waiting for the response (loading), the request failed (error), and the response arrived successfully (success, with actual data to show). Designing for all three upfront avoids a UI that looks broken during real-world network conditions.

Note: Sketch out what each of the three states should look like before writing any fetching code — it clarifies what state variables you actually need.

Warning: Only designing for the success case means users see a blank or frozen screen during loading, or nothing at all when a request fails.

Example: The Three States of a Data Request

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function App() {
  const [status] = React.useState("loading");
  if (status === "loading") return <p>Loading...</p>;
  if (status === "error") return <p>Something went wrong.</p>;
  return <p>Data loaded successfully!</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Using One Status String Instead of Multiple Booleans

Tracking isLoading and isError as two separate booleans allows nonsensical combinations, like both being true at once. A single status field with distinct string values (idle, loading, error, success) can only ever represent one state at a time, which is a more accurate model of reality.

Note: This pattern is sometimes called a 'state machine' — only one named state is ever active.

Warning: Two independent booleans can drift out of sync over time as more code paths are added, silently creating impossible combinations.

Example: Using One Status String Instead of Multiple Booleans

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function App() {
  const [status, setStatus] = React.useState("idle");
  const startLoading = () => setStatus("loading");
  return (
    <div>
      <button onClick={startLoading}>Load Data</button>
      <p>Status: {status}</p>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Giving the Error State a Retry Option

A good error state doesn't just say 'something went wrong' — it offers a way to try again, like a Retry button that re-runs the original fetch logic and resets the status back to loading.

Note: Extract the fetching logic into its own function so both the initial load and the Retry button can call the exact same code.

Warning: An error message with no way to recover (besides a full page refresh) is a frustrating dead end for users.

Example: Giving the Error State a Retry Option

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function App() {
  const [status, setStatus] = React.useState("error");
  const retry = () => setStatus("loading");
  if (status === "error") return <div><p>Failed to load.</p><button onClick={retry}>Retry</button></div>;
  return <p>Status: {status}</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>
Common Mistakes
  1. Only handling the success case and forgetting to design what the UI looks like while loading or after an error.
  2. Using a single boolean for three possible states, making 'loading AND error at once' representable when it shouldn't be.
  3. Not resetting the error state when a new request starts, leaving a stale error visible during a retry.
Chapter Summary
  • A data-fetching component typically has three states: loading, error, and success (with data).
  • Using a single status string (idle/loading/error/success) avoids invalid state combinations that separate booleans allow.
  • Each state should have its own distinct UI: a spinner/message, an error message with retry option, and the actual content.
  • Resetting error/loading state at the start of a new request keeps the UI accurate during retries.
Browser Support

No React-version restriction — built from useState and conditional rendering.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.