useEffect, What Is It Really?
Muhammad Athar

If you've read Alex Chen's article Why I Stopped Using useEffect for Data Fetching and found yourself nodding along but quietly wondering — wait, what exactly is useEffect and why does everyone have such strong opinions about it? — you're in the right place.
Or maybe you've seen it in Alex's piece on the key prop and custom hooks and it keeps appearing without a full explanation of what's actually happening when you call it.
This article is that explanation.
What "effect" means
Before the code, the word.
In React, your component's job is to describe what the UI should look like based on its current state and props. That's the render. It's pure — given the same inputs, you get the same output. No surprises.
An effect is anything that reaches outside of that pure render. Fetching data from a server. Setting a document title. Starting a timer. Adding an event listener to the window. Connecting to a WebSocket.
These things don't fit inside a render function because they interact with the world outside React. useEffect is React's designated place for all of that outside-world interaction.
Think of it this way: your render describes what should be on screen. Your effects describe what should happen as a result of what's on screen.
The basic shape
useEffect takes two arguments:
- A function — the effect itself, the code you want to run
- A dependency array — the list of values that determine when the effect runs again
The dependency array is where most of the confusion lives. Let's go through every case.
The three dependency array patterns
No dependency array — runs after every render
No array at all. This runs after the component renders and after every re-render. Almost never what you want. Included here so you know it exists and can recognise it.
Empty array — runs once on mount
The empty array tells React "this effect has no dependencies — run it once when the component mounts and never again." This is the pattern you see most often for one-time setup: connecting to a service, loading initial data, registering a global event listener.
Array with values — runs when those values change
This runs once on mount, then again any time userId changes. The array is a watchlist. When any value in it changes, the effect re-runs.
A real example: updating the page title
Here's useEffect doing something genuinely useful — keeping the browser tab title in sync with what's on screen.
Every time unreadCount changes, the effect runs and updates document.title. The browser tab reflects the current unread count. No library. No special hook. Twelve lines.
This is useEffect at its most natural — synchronising something outside React (the document title) with something inside React (component state).
The cleanup function
Some effects need to clean up after themselves. An event listener that keeps listening after the component is gone. A timer that keeps firing. A subscription that keeps receiving data.
If you don't clean these up, they leak. The component is gone from the screen but the effect is still running in the background.
The cleanup function is what useEffect returns:
React calls the cleanup function when the component unmounts — when it disappears from the screen. It also calls it before re-running the effect if the dependencies change, so the previous effect is always cleaned up before the new one starts.
Building something real: a live character counter
Here's a complete UI component that uses useEffect for two things — updating the document title and triggering a warning state — both in response to the same piece of state.
Notice the cleanup function in the effect: when the component unmounts — when the user closes the composer — document.title resets back to 'DesignDev.io'. Without it, the character count would stay in the tab title forever.
Drop this into any React project and it works. That's the goal of every example here.
What useEffect is not for
Two common misuses worth naming:
Deriving state. If you're using useEffect to compute a value from existing state and set it into new state, you don't need an effect. Compute it directly during render:
Fetching data for the primary content of a component. This is what Alex covers in detail in Why I Stopped Using useEffect for Data Fetching. The short version: for primary data loading, useQuery from TanStack Query handles the race conditions, caching, and deduplication that a plain useEffect fetch misses.
Where this leads
Now that useEffect makes sense, a few natural next steps:
useCallback — a hook that keeps a function reference stable between renders. Relevant when a function is a dependency in your effect array and you don't want the effect running on every render. We'll cover this properly in Did you know useCallback can actually do this?
useRef — stores a value that persists across renders without triggering a re-render. Useful inside effects when you need to track a previous value or hold a reference to a DOM node.
useQuery from TanStack Query — the right tool for data fetching that useEffect is often misused for. If you're fetching data in a component and want caching, deduplication, and loading states handled automatically, this is where to go next.
useSyncExternalStore — for subscribing to external data sources (browser APIs, third-party stores) in a way that's safe with React's concurrent rendering. More advanced, but worth knowing exists.
useEffect is React's door to the outside world. Once you understand that it runs after the render, that the dependency array controls when it re-runs, and that the cleanup function runs before it runs again and when the component unmounts — most of the confusion clears up.
The character counter above is yours. Build something with it.
This article was prompted by the React Foundations series — particularly Why I Stopped Using useEffect for Data Fetching and How to Build a Custom Hook That Actually Earns Its Abstraction by Alex Chen. If you haven't read those yet, they're the natural next step after this one.
Muhammad Athar is the founder and engineer behind DesignDev.io. He writes the "From the Builder" series — concept explainers triggered by the site's own articles, always grounded in something you can actually build.
Photo by Bradyn Trollip on Unsplash

