Fetching Data in React with useEffect
Jun 20, 2026 · 8 min read
Sooner or later your app needs data from somewhere — a list of posts, a user profile, search results. In React, the basic tool for this is useEffect plus the browser's fetch. The happy path is short; the honest version handles a few things the quick tutorials skip.
The basic pattern
Fetch inside an effect so it runs after render, and keep three pieces of state: the data, a loading flag, and any error.
function User({ id }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/api/users/' + id)
.then(res => res.json())
.then(data => setUser(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, [id]);
if (loading) return <p>Loading…</p>;
if (error) return <p>Something went wrong.</p>;
return <h1>{user.name}</h1>;
}Those three states are exactly what your UI needs to decide what to show. Rendering one of three outcomes is a job for conditional rendering.
Why id is in the dependency array
The effect lists [id] as its dependency, so it re-fetches whenever id changes. Forgetting this is a classic bug — the data loads once and never updates when the prop changes. If the dependency array still feels fuzzy, the useEffect guide breaks it down.
The race condition nobody mentions
If id changes quickly, two requests can be in flight at once — and the slower one might resolve last and overwrite the newer data. Guard against it with a cleanup flag:
useEffect(() => {
let active = true;
fetch('/api/users/' + id)
.then(res => res.json())
.then(data => {
if (active) setUser(data);
});
return () => {
active = false; // ignore a stale request's result
};
}, [id]);When to reach for a library
Once you need caching, refetching, or the same data in several components, hand-rolled effects get repetitive fast. That's the moment to consider a data library like TanStack Query or SWR — or, at the very least, to wrap your fetch logic in a reusable custom hook such as useUser(id).
Key takeaways
- Track three things: the data, a loading flag, and an error.
- Put every value the request depends on (like
id) in the dependency array. - Use a cleanup flag to ignore stale responses and dodge race conditions.
- Reach for a data library once you need caching and refetching.
Want the structured path? Explore the React roadmap or browse more articles.