The Hook That Solved Everything (Except It Didn't)
When you first learn React, useEffect feels magical. Need to sync some state? There's a hook for that. Want to fetch data? Hook. Combine two props into one? Another hook. After a few tutorials, it starts to feel like useEffect is the answer to almost every problem. And then you build something bigger.
Today is July 15, 2026, and the React community is having a reckoning with useEffect. According to a recent article by Alejandro, a senior developer who's worked on larger applications, the pattern that once felt essential has become something he uses roughly 80% less than he did in his earlier years. The reason isn't that useEffect is bad — it's that most of the problems developers were solving with it have much simpler solutions.
This matters right now because it's becoming clear that learning React well means learning when not to use its most famous tool.
What useEffect Was Actually Built For
Let's start with the official story. React's documentation describes effects as a way to "synchronize your component with external systems." That's the key word: external. Things outside of React itself — like network requests, WebSocket connections, timers, browser APIs, subscriptions, or third-party libraries.
If your effect isn't talking to something outside React, there's a good chance you don't actually need one.
Five Things You're Probably Doing Wrong
Deriving values inside an effect
One of the most common patterns is using useEffect to combine props or state into a new value. For example:
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
This works, but React renders twice: once with the original empty state, the effect runs, state changes, and React renders again. There's no reason for that extra render.
Instead, just calculate the value during render:
const fullName = `${firstName} ${lastName}`;
Simpler, faster, fewer renders.
Copying props into state
Another common pattern is syncing a prop into local state:
const [user, setUser] = useState(props.user);
useEffect(() => {
setUser(props.user);
}, [props.user]);
This creates two sources of truth. Usually one ends up getting updated while the other doesn't. Unless you specifically need a local editable copy (which is rare), just use the prop directly. The simpler approach:
function Profile({ user }) {
return <h2>{user.name}</h2>;
}
One source of truth. Much easier to debug.
Filtering or transforming lists inside effects
You've probably seen code that filters a list inside an effect and stores the result in state:
const [filteredUsers, setFilteredUsers] = useState([]);
useEffect(() => {
setFilteredUsers(users.filter(user => user.active));
}, [users]);
Again, this is unnecessary state. Just compute it during render:
const filteredUsers = users.filter(user => user.active);
If the computation is expensive, there's a hook for that — but it's not useEffect. It's useMemo, which memoizes the result so it only recalculates when dependencies change:
const filteredUsers = useMemo(() => {
return users.filter(user => user.active);
}, [users]);
But remember: useMemo is an optimization, not a replacement for thinking about your code.
Using effects for debugging
There's one place where effects make sense temporarily: debugging. Logging whenever a value changes is genuinely useful:
useEffect(() => {
console.log(user);
}, [user]);
But these should be deleted before merging your code.
Fetching data the old way
A few years ago, almost every React project had this pattern:
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(setUsers);
}, []);
It works, but it doesn't do much. There's no error handling, no loading state, no retry logic, no deduplication if the component mounts twice. Most teams ended up building all of that themselves.
Now there are better options. Libraries like TanStack Query and SWR handle caching, retries, background refetching, loading states, error states, and deduplication automatically. Instead of writing an effect, you use a hook:
const { data, isLoading } = useQuery({
queryKey: ["users"],
queryFn: getUsers
});
Much less code. Far fewer bugs. Better developer experience.
When Effects Hide Real Problems
There's a pattern Alejandro noticed over time: when a component has lots of effects, it's usually doing too much. Maybe it's fetching data, filtering it, sorting it, formatting it, validating it, and handling events all inside one component. That's not an effect problem — that's an architecture problem.
Often, splitting responsibilities into smaller hooks or components removes half the effects automatically.
When You Actually Do Need useEffect
None of this means "never use useEffect." There are plenty of legitimate reasons:
WebSocket connections: You need to open a connection when the component mounts and close it when it unmounts. That's exactly what effects are for.
Timers: If you need to poll for updates every 5 seconds, setInterval inside an effect with proper cleanup makes sense.
Browser APIs: Listening to the window's resize event or syncing with localStorage are side effects.
Third-party libraries: Initializing a chart library or analytics SDK — these need to run when the component mounts.
These are the exact scenarios effects were designed for: synchronizing with something outside React.
The Question That Changes Everything
Before writing an effect, Alejandro asks himself one thing: "Am I synchronizing with an external system, or am I compensating for my component design?"
That question alone, he says, has removed a surprising amount of unnecessary code from his projects.
Conclusion
useEffect isn't bad. It's just easier to overuse than most React hooks. Modern React developers tend to reach for derived values, keep props as props, use query libraries for server state, and save effects for things that actually need them. The result is fewer renders, less state to manage, fewer bugs, and components that are much easier to understand months later.
Merits
- Reduces unnecessary re-renders and improves performance
- Simplifies code by avoiding redundant state and effects
- Easier to debug and maintain components with fewer side effects
- Query libraries handle complex data-fetching logic automatically
- Better component architecture when effects highlight design issues
- Less state means fewer places for bugs to hide
Demerits
- Requires learning when to use alternatives (useMemo, custom hooks, query libraries)
- Developers used to effects-heavy patterns may need to change their habits
- Some legacy projects rely heavily on useEffect patterns and can't be refactored overnight
- Not every team has adopted libraries like TanStack Query yet
- Inline computations can be less readable if not done carefully
Caution
This article is educational and explains modern React patterns discussed in community articles. The code examples are illustrative only — if you use them in a real project, replace placeholder values with your actual API endpoints and logic. Always verify patterns against the original source before relying on them for production code. React and the ecosystem evolve quickly; check the official React documentation and library docs (TanStack Query, SWR) for the most current guidance.
Frequently asked questions
-
When should I use useEffect instead of deriving state? — Use
useEffectonly when you're synchronizing with an external system (APIs, timers, browser events). If you're just transforming existing data, derive it during render. -
Is useMemo better than useEffect for calculations? —
useMemooptimizes expensive calculations, but use it thoughtfully. Most calculations are fast enough to compute every render — only memoize when profiling shows it matters. -
Should I replace all my useEffect data fetching? — Libraries like TanStack Query are much more powerful than
useEffect, but migrating a large codebase takes time. Start with new features and refactor gradually. -
What's the difference between TanStack Query and SWR? — Both are query libraries that handle caching and refetching. TanStack Query is more full-featured; SWR is simpler and lighter. Pick based on your project's needs.
-
Can I still use useEffect for debugging? — Yes, but delete the debug effects before merging code. Use your browser's DevTools instead for production debugging.
-
How do I know if my component is doing too much? — If it has more than two or three effects, or if effects depend on many different things, consider breaking it into smaller components or custom hooks.
-
Does avoiding useEffect make React harder to learn? — Not really — it means learning React's core model better. Understanding when not to use a feature often clarifies what it's actually for.
-
What about WebSockets and browser APIs — do those always need useEffect? — Yes, if you're managing the lifecycle of something external to React,
useEffectwith proper cleanup is the right tool.
Tags
#react #useeffect #javascript #webdev #frontend #reacthooks #modernreact #bestpractices


Responses
Sign in to leave a response.