Struggling with infinite loops or double API calls? Learn how to use the dependency array and cleanup functions to keep your UI in sync without the bugs.

Think of the dependency array as a bouncer standing at the edge of that bridge. Its entire job is to look at a list of items you’ve given it and ask one question: 'Has anything on this list changed since the last time I let you through?'
This behavior is typically caused by React 18’s StrictMode in a development environment. StrictMode intentionally mounts, unmounts, and remounts components to stress-test effects and ensure they are resilient to real-world user navigation. If your effect lacks a proper cleanup function or isn't "idempotent," you will see duplicate calls. This serves as a diagnostic tool to warn you that your "bridge" to the outside world might be fragile.
A stale closure occurs when a useEffect hook uses a variable, such as a prop or state, but that variable is omitted from the dependency array. Because the effect only "sees" the version of the variable from the render in which it was created, it becomes stuck with an outdated "photograph" of that data. To prevent this, you should be honest with the "gatekeeper" (the dependency array) and include all variables the effect relies on, or use the ESLint "exhaustive-deps" rule to catch these omissions.
Race conditions happen when multiple asynchronous requests are fired in quick succession, and an older request finishes after a newer one, overwriting the UI with outdated data. To fix this, you should use a cleanup function to "cancel" the previous request. This can be done by using an AbortController to kill the fetch call or by setting a boolean flag (like ignore = true) in the cleanup function to ensure the component ignores the result of any request that is no longer relevant.
You should use a cleanup function whenever your effect creates a resource or a connection to an external system that needs to be "undone" to avoid memory leaks or "zombie" updates. Common examples include clearing a setInterval, removing window event listeners, closing WebSocket connections, or aborting API fetches. The cleanup function runs not only when the component unmounts but also before every re-execution of the effect to ensure old connections are torn down before new ones are built.
React uses "shallow comparison" or referential equality to check if items in a dependency array have changed. Because objects and arrays are often re-created on every render, React sees them as "new" even if their internal content is identical. This triggers the effect, which might update state, causing a re-render and creating another new object, leading to an infinite loop. To avoid this, you can depend on primitive values (like strings or booleans) or use useMemo and useCallback to maintain a stable reference to the object or function.
From Columbia University alumni built in San Francisco
"Instead of endless scrolling, I just hit play on BeFreed. It saves me so much time."
"I never knew where to start with nonfiction—BeFreed’s book lists turned into podcasts gave me a clear path."
"Perfect balance between learning and entertainment. Finished ‘Thinking, Fast and Slow’ on my commute this week."
"Crazy how much I learned while walking the dog. BeFreed = small habits → big gains."
"Reading used to feel like a chore. Now it’s just part of my lifestyle."
"Feels effortless compared to reading. I’ve finished 6 books this month already."
"BeFreed turned my guilty doomscrolling into something that feels productive and inspiring."
"BeFreed turned my commute into learning time. 20-min podcasts are perfect for finishing books I never had time for."
"BeFreed replaced my podcast queue. Imagine Spotify for books — that’s it. 🙌"
"It is great for me to learn something from the book without reading it."
"The themed book list podcasts help me connect ideas across authors—like a guided audio journey."
"Makes me feel smarter every time before going to work"
From Columbia University alumni built in San Francisco
