Fetching Data with fetch()
In this 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
<!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
<!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
<!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>
- Forgetting fetch() only rejects on network failure, not on HTTP error statuses like 404/500 — you must check response.ok yourself.
- Not calling .json() (or another body-reading method) to actually parse the response body.
- Running fetch() directly in the component body instead of inside useEffect, causing an infinite loop of requests.
- 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.
fetch() is supported in all modern browsers (not IE11 without a polyfill); works with any React version.
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: