How do you fetch data with useEffect and prevent memory leaks?

This tests useEffect async lifecycle and cleanup. A good answer places fetch inside useEffect, uses an ignore flag or AbortController to block state updates after unmount, and includes deps. A red flag is skipping cleanup and letting a late response set state.
What's really being asked
This question evaluates your understanding of the useEffect lifecycle for side effects that involve asynchronous network requests. The interviewer wants to see that you know how to initiate a fetch when a component mounts, protect against updates after unmount, manage the dependency array correctly, and return cleanup logic. It also surfaces whether you understand the difference between the setup function and an async setup function, and how React handles effect cleanup during unmount or re-render.
The full answer
First, place the fetch call inside the useEffect setup function rather than outside the hook or directly in the component body. Second, declare every reactive value used inside the effect, such as the API URL or query parameters, in the dependency array so the effect re-runs when inputs change. Third, handle the unmount case by returning a cleanup function that either aborts the request with an AbortController or ignores the result with a boolean flag. Fourth, avoid making the setup function itself async because useEffect expects the return value to be either undefined or a cleanup function, not a Promise.
The mistakes people make
A frequent mistake is writing an async function directly as the useEffect callback, which implicitly returns a Promise and breaks the cleanup contract. Another red flag is omitting the dependency array or leaving out reactive dependencies, which causes stale closures or infinite refetch loops. Simply calling fetch without any cancellation or ignore logic is also weak because it can trigger a memory leak warning or set state on an unmounted component. Finally, putting fetch in the component body without useEffect causes a request on every render.
What usually comes next
The interviewer might ask how you would handle race conditions when the dependency changes quickly, such as a user typing in a search box. They may also ask why you should not use useEffect for data fetching in production React apps, which opens a discussion about server components, React Query, or the fetch-then-render pattern. Another follow-up is how StrictMode intentionally double-invokes effects in development to help you test your cleanup logic.
A concrete example
Imagine a UserProfile component that receives a userId prop. You would write useEffect with userId in the dependency array. Inside the setup function, you create an AbortController, pass its signal to fetch, and update state only when the response arrives. In the cleanup function, you call controller.abort. If the component unmounts or userId changes before the fetch finishes, the browser cancels the request and the effect does not attempt to update state.
Interview question
Which approach correctly fetches data inside useEffect while preventing memory leaks when the component unmounts?
- a.Create an async function inside the effect, invoke it, and return a cleanup function that aborts the request.Correct
- b.Declare the useEffect callback as async so you can await the fetch response directly.
- c.Include every reactive dependency in the array but skip cleanup because React automatically cancels pending requests on unmount.
- d.Call fetch directly in the component body and update state when the promise resolves.
Why? this is the answer
Creating an async function inside the effect allows the outer callback to remain synchronous and return an abort cleanup function. Declaring the useEffect callback itself as async is a common mistake because it returns a Promise, breaking React's cleanup contract.
Just read this? Test yourself on what you have been reading.
Read the original → react.dev
- #react
- #useeffect
- #data-fetching
- #cleanup
- #memory-leaks
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on react — each one lists the topics its interview covers.
See open roles