React When Not to Use useEffect

In the last lesson you learned React useEffect Common Mistakes, so you know useEffect is easy to get wrong. The bigger surprise is that the most common mistake is using it at all when you didn’t need it.

🤔 Why people overuse useEffect

Here’s the pain. You learn that effects run after render and let you “do stuff”, so it feels like the place for everything.

  • It looks like the tool for any kind of side work, so people stuff all sorts of logic in there.
  • But an effect runs after the screen has already painted, so it adds an extra render and extra timing to think about.
  • Most of the time the work can happen earlier and simpler, either during render or right inside an event handler.

So the real skill is knowing when an effect is the wrong tool. An unnecessary effect makes your code slower and harder to follow, and it opens the door to stale-value bugs.

🧮 Don’t use an effect to compute a value from state

The first case is the most common by far. You have some state, and you want another value built from it.

  • People reach for an effect to keep that second value in sync, but you don’t need to.
  • If a value can be worked out from state or props you already have, just calculate it while the component renders.
  • A value you compute during render instead of storing is called derived state, and it is always correct, because it is rebuilt fresh on every render.
  • An effect that copies state into more state is slower and can fall out of sync, since the effect runs a beat after the render.

Look at the broken version next to the clean one. The bad one keeps fullName in its own state and uses an effect to update it.

// ❌ Wrong - effect copies state into more state
function Profile() {
const [firstName, setFirstName] = useState("Alex");
const [lastName, setLastName] = useState("Kumar");
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`); // extra render, can drift
}, [firstName, lastName]);
}
// ✅ Right - just compute it during render
function Profile() {
const [firstName, setFirstName] = useState("Alex");
const [lastName, setLastName] = useState("Kumar");
const fullName = `${firstName} ${lastName}`; // always correct, no effect
}

Here’s what each version actually does:

  • The effect version renders once with the old fullName, then the effect runs and sets it, then React renders again.
  • The computed version gets it right the first time, with no extra state and no effect at all.

A quick test before adding an effect

Ask yourself: can I work this value out from state or props I already have? If yes, calculate it during render. Don’t store it in state and don’t sync it with an effect.

🖱️ Don’t use an effect to react to a user event

The second case is reacting to something the user did, like a click.

  • People sometimes set a flag in state, then watch that flag with an effect to do the real work. That’s a long way around.
  • When the user clicks a button, you already know exactly when it happened. So do the work right there in the click handler.
  • An effect doesn’t know why it ran. It only sees that some value changed, so it can’t tell a real click apart from any other reason that value moved.
  • Putting the logic in the event handler keeps the cause and the action in one place, which is easier to read and to debug.

Here’s the roundabout version next to the direct one. Say we want to send a message and show a confirmation when a button is clicked.

// ❌ Wrong - set a flag, then watch it with an effect
function SendButton() {
const [clicked, setClicked] = useState(false);
useEffect(() => {
if (clicked) {
sendMessage(); // why did this run? the effect can't tell
setClicked(false);
}
}, [clicked]);
return <button onClick={() => setClicked(true)}>Send</button>;
}
// ✅ Right - just do the work in the click handler
function SendButton() {
const handleClick = () => {
sendMessage(); // the click happened, so act now
};
return <button onClick={handleClick}>Send</button>;
}

See the difference in the two versions:

  • The effect version needs an extra state variable, an effect to watch it, and a reset to clear it.
  • The direct version is one function that runs the moment the button is clicked. Same result, far less code.

Effects are not for events

An effect is for staying in sync with something, like the screen or an outside system. It is not for “this happened, now do that”. If a piece of logic is the response to a specific user action, it belongs in the handler for that action.

🔄 Don’t use an effect to transform data for the screen

The last case is shaping data before you show it, like filtering a list or sorting it.

  • People filter inside an effect and store the result in another state variable, but you can do it inline instead.
  • Filtering, sorting, or mapping a list for display is just another value built from state, so it is derived state too.
  • Do the transform during render, right where you use the result. No effect, no second copy of the list to keep in sync.
  • This also avoids a classic bug where the original list updates but the filtered copy still shows the old data, because the effect hasn’t caught up yet.

Here’s the heavy version next to the simple one. We want to show only the items that match a search box.

// ❌ Wrong - effect filters into a second state variable
function ProductList({ products }) {
const [query, setQuery] = useState("");
const [visible, setVisible] = useState(products);
useEffect(() => {
setVisible(products.filter((p) => p.name.includes(query)));
}, [products, query]); // extra state, can lag behind
return <List items={visible} />;
}
// ✅ Right - filter during render, right where you use it
function ProductList({ products }) {
const [query, setQuery] = useState("");
const visible = products.filter((p) => p.name.includes(query));
return <List items={visible} />;
}

Here’s how the two stack up:

  • The effect version keeps a whole second list in state and runs after each render to refill it.
  • The inline version builds the filtered list fresh every render, so it can never be out of date.
  • If the filtering ever gets genuinely slow, the fix is useMemo, not an effect.

Transforming for display is derived state

Filtering, sorting, and mapping for the screen are all the same idea as computing a value. They are derived from existing data, so they belong in render, not in an effect with their own state.

✅ So when DO you actually need an effect?

Effects are not bad. They have a real job. It’s just a narrower job than people think.

  • Use an effect to sync your component with something outside React, like the document title, a setInterval timer, or a subscription to a chat server.
  • Use one for a network request that should run when the component appears or when some value changes, not in response to a single click.
  • The simple line to remember: if the work connects to an external system, an effect fits. If it can be computed during render or done in an event handler, skip it.

So before you write useEffect, pause and check which of these you’re really doing. Most of the time, the answer is “neither”, and you can delete the effect.

The one-question filter

Before adding an effect, ask: is this syncing with something outside React? If no, it is almost always either derived state (compute it in render) or an event response (do it in the handler).

🧩 What You’ve Learned

  • ✅ Beginners overuse useEffect, and the most common mistake is using one when you didn’t need it at all
  • ✅ A value you can work out from state or props is derived state — compute it during render, don’t store it and sync it with an effect
  • ✅ Logic that responds to a user action belongs in the event handler, not in an effect that watches a flag
  • ✅ Filtering, sorting, and mapping data for the screen is derived state too, so do it inline in render
  • ✅ Effects are for syncing with things outside React, like the document title, timers, or subscriptions
  • ✅ Before writing an effect, ask if it can be computed in render or done in a handler instead

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    You have firstName and lastName in state and need fullName. What is the best approach?

    Why: fullName can be derived from existing state, so compute it during render. Storing it as separate state and syncing it with an effect adds an extra render and can drift out of sync.

  2. 2

    You want to send a message when a button is clicked. Where should that logic go?

    Why: Sending the message is the response to a specific user action, so it belongs in the click handler. An effect can't tell why it ran, so reacting to events in an effect is the wrong tool.

  3. 3

    You need to show only the list items that match a search box. What should you do?

    Why: Filtering for display is derived state, so do it during render. Keeping a second filtered list in state and refilling it with an effect can lag behind the real list.

  4. 4

    Which of these is a job that an effect is actually right for?

    Why: Effects are for syncing your component with external systems, like the document title, timers, or subscriptions. Derived values and event responses do not need an effect.

🚀 What’s Next?

So now you know useEffect is a narrow tool, not a catch-all, and you can skip it for derived values and event handling. Sometimes you really do need to hold on to a value across renders without causing a re-render, like a timer id or a DOM reference, and that’s a different hook.

React useRef Hook

Share & Connect