🎣 Understanding useEffect
in React: Managing Side Effects the Right Way
React’s declarative model makes building UIs clean and efficient—but what happens when your app needs to interact with the outside world? That’s where the useEffect
hook comes in.
In this blog post, we’ll walk through:
useEffect
workspopstate
Let’s dive in!
In React, side effects refer to operations that interact with the world outside of the component. These are things that React itself doesn’t control, such as:
setTimeout
or setInterval
React gives us the useEffect
hook to manage these operations safely.
useEffect
SyntaxHere’s how you typically use useEffect
:
useEffect(() => {// 1. Run side-effect heredoSomething()// 2. Optional cleanup functionreturn () => {doSomeCleanup()}}, [dep1, dep2]) // 3. Dependency array
[]
) means the effect only runs on mount/unmount.React’s lifecycle for function components with hooks can be broken into three phases:
useState
/useReducer
.useLayoutEffect
) are applied.useEffect
) run after the paint.useEffect
and useLayoutEffect
.🎨 The React Hook Flow diagram beautifully visualizes this, color-coded by lifecycle phase:
Suppose you have a search form that updates the query parameter in the URL when a user submits:
setGlobalSearchParams({ query: 'cat' }) // Updates URL without a page reload
But when the user hits the back button, the input field doesn’t update accordingly. To fix this, you can use the popstate
event:
useEffect(() => {function handlePopState() {setQuery(getQueryParam()) // Sync input with URL}window.addEventListener('popstate', handlePopState)return () => {window.removeEventListener('popstate', handlePopState)}}, [])
You can initialize state from URL params only once on mount using a lazy initializer:
const [query, setQuery] = useState(getQueryParam)
This avoids unnecessary recalculations on re-renders.
Here’s the problem: if you add an event listener every time your component mounts but never remove it, you’re leaking memory. And in dynamic applications, components are often added/removed repeatedly.
Let’s say you toggle a form with a checkbox:
popstate
listener.Each time you add an event listener, a callback function is retained in memory. If you don’t remove the listener, that function (and all the variables it closes over) remains in memory—forever.
This is called a memory leak.
Always clean up your effects like this:
useEffect(() => {function handleEvent() {// Do something}window.addEventListener('some-event', handleEvent)return () => {window.removeEventListener('some-event', handleEvent)}}, [])
This ensures that every time your component unmounts, the listener is removed and memory is freed.
If you want to test for memory leaks:
Concept | Summary |
---|---|
useEffect | Runs code after DOM updates |
Dependencies | Tell React when to re-run the effect |
Cleanup function | Avoids memory leaks by unsubscribing/listening clean-up |
popstate event | Lets you react to back/forward browser actions |
Lazy state initialization | Improves performance by computing initial state only once |
Memory leaks | Result from not cleaning up subscriptions or listeners |
Quick Links
Legal Stuff
Social Media