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

Fetching Data with fetch()

Using fetch() in React is like sending a messenger out to grab information from another building and bring it back to show on your page.

Making a Basic fetch() Request

fetch(url) sends an HTTP request and returns a Promise that resolves to a Response object once the headers arrive. Calling .json() on that response (itself returning another Promise) parses the response body, giving you the actual data.

Note: Use async/await inside useEffect for the clearest, most readable version of this two-step process.

Warning: fetch()'s Promise resolves even for a 404 or 500 response — it doesn't reject just because the server returned an error status.

Example: Making a Basic fetch() 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 [data, setData] = React.useState(null);
  React.useEffect(() => {
    setData({ message: "Simulated fetch result" });
  }, []);
  return <p>{data ? data.message : "Loading..."}</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Checking response.ok for HTTP Errors

Since fetch() doesn't reject on HTTP error statuses, checking response.ok (true for 200-299 statuses) is the standard way to detect a failed request and handle it explicitly, usually by throwing an error to be caught separately.

Note: response.status gives the exact numeric status code if you need to branch on specific errors (like 401 vs 404).

Warning: Skipping this check means a 404 response silently gets treated as successful, potentially rendering broken or missing data as if it were valid.

Example: Checking response.ok for HTTP Errors

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 [error, setError] = React.useState(null);
  React.useEffect(() => {
    const ok = false;
    if (!ok) setError("Request failed with status 404");
  }, []);
  return <p style={{color: "red"}}>{error || "Success"}</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Fetching Inside useEffect, Not the Render Body

Calling fetch() directly in a component's body (outside useEffect) runs it on every single render, including the re-render caused by the fetch itself, creating an infinite request loop. Wrapping it in useEffect with an empty dependency array runs it exactly once, after the initial render.

Note: An empty dependency array ([]) means 'run this effect only once, after the first render' — the standard pattern for a one-time data fetch.

Warning: Calling setState from a fetch's result without wrapping the fetch in useEffect causes React to re-render and re-fetch endlessly.

Example: Fetching Inside useEffect, Not the Render Body

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 [data, setData] = React.useState(null);
  React.useEffect(() => {
    // fetch("/api/data").then(r => r.json()).then(setData);
    setData("Loaded once, safely, via useEffect");
  }, []);
  return <p>{data || "Loading..."}</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>
Common Mistakes
  1. Forgetting fetch() only rejects on network failure, not on HTTP error statuses like 404/500 — you must check response.ok yourself.
  2. Not calling .json() (or another body-reading method) to actually parse the response body.
  3. Running fetch() directly in the component body instead of inside useEffect, causing an infinite loop of requests.
Chapter Summary
  • fetch() is the built-in browser API for making HTTP requests, returning a Promise.
  • fetch() only rejects on network errors — checking response.ok is required to catch HTTP error statuses.
  • response.json() (also a Promise) parses the response body as JSON.
  • Data fetching in a component belongs inside useEffect, not directly in the render body.
Browser Support

fetch() is supported in all modern browsers (not IE11 without a polyfill); works with any React version.

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.