Himanshu SharmaSoftware engineer
Let’s talk

React · Lesson 25 of 38

You might not need an effect

Spot when a React effect is the wrong tool and use derived values, event handlers, the key prop and lifted state for simpler, faster code.

18 minEffects and dataUpdated 24 October 2026

By the end of this lessonRecognise code that does not need an effect and replace it with derived values, event handlers or a key.

Effects are the escape hatch for talking to the world outside React. Because they are easy to reach for, beginners (and experienced developers) use them for jobs that React can do better by itself. Needless effects make code slower and harder to follow: each causes an extra render, and the delay lets the screen flicker or briefly show inconsistent values. This lesson lists the common cases and the simpler fix for each.

1. Do not use an effect to transform data for rendering

The most common mistake is keeping a derived value in state and updating it with an effect:

jsx
import { useEffect, useState } from "react";

function App() {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");

  // Redundant state and an unnecessary effect
  const [fullName, setFullName] = useState("");
  useEffect(() => {
    setFullName(firstName + " " + lastName);
  }, [firstName, lastName]);

  return (
    <div>
      <input name="first" value={firstName} onChange={e => setFirstName(e.target.value)} placeholder="First" />
      <input name="last" value={lastName} onChange={e => setLastName(e.target.value)} placeholder="Last" />
      <p>Full name: {fullName}</p>
    </div>
  );
}

export default App;
result
Full name: Asha Rao

It works, but every keystroke renders twice: once for the typed letter (with the old fullName), then again when the effect sets fullName. For a moment the screen is inconsistent. Delete the state and the effect and calculate the value during rendering:

jsx
import { useState } from "react";

function App() {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");

  const fullName = (firstName + " " + lastName).trim();   // calculated every render

  return (
    <div>
      <input name="first" value={firstName} onChange={e => setFirstName(e.target.value)} placeholder="First" />
      <input name="last" value={lastName} onChange={e => setLastName(e.target.value)} placeholder="Last" />
      <p>Full name: {fullName}</p>
    </div>
  );
}

export default App;
result
Full name: Asha Rao

The rule: if you can calculate something from props or state, do not put it in state, and do not use an effect to keep it updated. This covers filtering and sorting lists, totals, counts, formatting and class names.

Expensive calculations

If the calculation is genuinely slow (thousands of items), do not move it to an effect. Wrap it in useMemo, which caches the result until its inputs change. First measure: most calculations are fast enough that no caching is needed. The memoization lesson shows how.

2. Do not use an effect to handle user events

Suppose pressing "Buy" must show a message. You might write a bought state and an effect that reacts to it:

jsx
// Avoid: the click causes a state change, the state change triggers an effect
const [bought, setBought] = useState(false);
useEffect(() => {
  if (bought) showNotification("Thanks for your order!");
}, [bought]);

Why avoid it? The effect cannot tell whether bought became true because of the user's click or for some other reason, it runs a render late, and it can run again at surprising moments (for example when the component is remounted). Put the code where the cause is:

jsx
import { useState } from "react";

function App() {
  const [message, setMessage] = useState("");

  function handleBuy() {
    console.log("POST /api/order");               // the request belongs to the click
    setMessage("Thanks for your order!");
  }

  return (
    <div>
      <button onClick={handleBuy}>Buy</button>
      <p>{message}</p>
    </div>
  );
}

export default App;
result
Buy
Thanks for your order!
CONSOLE
POST /api/order

A good test: "if I describe why this code runs, is the answer because the user did X?" Then it is an event handler. "Because the component is shown" or "because this value changed and something outside React must follow" suggests an effect.

3. Resetting state when a prop changes: use a key

A common situation: a form or editor keeps local state, and when the user switches to another item (another profile id), the old text should disappear. Beginners write an effect that resets the state when the id changes:

jsx
useEffect(() => {
  setComment("");          // reset when the profile changes
}, [userId]);

That renders once with the stale comment before clearing it. The cleaner way is to tell React that these are different components with a key. When the key changes, React destroys the old instance, with all its state, and builds a fresh one:

jsx
import { useState } from "react";

function CommentBox() {
  const [comment, setComment] = useState("");
  return (
    <div>
      <textarea value={comment} onChange={e => setComment(e.target.value)} placeholder="Write a comment" />
      <p>{comment.length} characters typed</p>
    </div>
  );
}

function App() {
  const [userId, setUserId] = useState(1);

  return (
    <div>
      <button className="next" onClick={() => setUserId(userId + 1)}>Next user</button>
      <h3>Profile {userId}</h3>
      <CommentBox key={userId} />
      <p>Showing the box for user {userId}</p>
    </div>
  );
}

export default App;
result
Next user
Profile 2
0 characters typed
Showing the box for user 2

I typed a comment for user 1 (10 characters), then pressed "Next user": the counter for user 2 starts again at 0 because the key changed. Remove key={userId} and the typed text stays, which is the bug. This also resets anything inside CommentBox: its own children, refs, and so on.

If you only need to adjust part of the state when a prop changes, restructure instead: store the id of the selection rather than the object (as in the state-structure lesson), or calculate the value during rendering.

4. Do not chain effects that set state for each other

jsx
// Avoid: effect A sets state that triggers effect B that sets state that triggers effect C...
useEffect(() => { setRound(r => r + 1); }, [cardsPlayed]);
useEffect(() => { setGameOver(round > 5); }, [round]);

Each link adds a render and makes the logic hard to follow. Compute what you can during rendering (const gameOver = round > 5), and do the rest in the event handler that started the chain, where you can call all the setters together and React batches them into one render.

5. Do not use an effect to notify the parent

When a child's state changes, it might call onChange(value) from an effect to tell the parent. Call it in the handler instead, at the moment of the change:

jsx
// Avoid
useEffect(() => { onChange(isOn); }, [isOn, onChange]);

// Prefer: update state and tell the parent in the same event
function handleClick() {
  const next = !isOn;
  setIsOn(next);
  onChange(next);
}

Even better, remove the duplicate state: make the child controlled and let the parent own isOn, as in the lifting-state lesson.

6. Subscribing to an external store

When a component needs to read something that changes outside React (a browser value like the online status, or a state library's store), do not hand-write an effect with setState. React provides useSyncExternalStore, which does it safely, without tearing (parts of the screen showing different values) in concurrent rendering:

jsx
import { useSyncExternalStore } from "react";

function subscribe(callback) {
  window.addEventListener("online", callback);
  window.addEventListener("offline", callback);
  return () => {
    window.removeEventListener("online", callback);
    window.removeEventListener("offline", callback);
  };
}

function useOnlineStatus() {
  return useSyncExternalStore(subscribe, () => navigator.onLine, () => true);
}

function App() {
  const isOnline = useOnlineStatus();
  return <p>You are {isOnline ? "online" : "offline"}.</p>;
}

export default App;

The three arguments are: how to subscribe, how to read the current value in the browser, and a fallback for server rendering. Libraries such as Redux and Zustand use this hook internally. Writing your own is rare; the point is to know the correct tool exists.

7. Sending a request because of a click, or initialising the app

  • Sending a request that a user triggered belongs in the handler (the fetching lesson).
  • One-time app setup (reading config, checking login) can live at module level, outside components, so it runs once per page load, not once per mount. This avoids StrictMode's double run of an effect doing it.
  • Fetching on screen load is a legitimate effect (or better, a framework or data library), and needs the cleanup shown in the fetching lesson.

So what are effects for?

Use an effect when the component being on the screen is the reason for the code, and the code touches something outside React:

  • connecting to a server or a WebSocket,
  • subscribing to browser events (resize, keyboard, online/offline),
  • starting and stopping timers or animations,
  • controlling widgets that React does not render (maps, video players, charts),
  • setting document.title,
  • fetching data for the displayed screen (with cleanup).

A quick decision guide

Situation Use
A value computed from props or state a variable in the component body
A slow calculation useMemo
Reset state when an id changes key on the component
Something the user caused event handler
Share state between components lift it to the parent
Read an external store useSyncExternalStore
Sync with a network, timer, subscription or widget effect

Common mistakes

  • Keeping derived values in state and syncing them with an effect.
  • Setting state in an effect with no way for it to stop (loops).
  • Using an effect to run code after a click.
  • Writing an effect to reset state instead of using a key.
  • Using an effect to tell the parent about a state change.
  • Putting console.log or analytics in an effect and being surprised by double calls in development; for analytics of "page view" use a proper cleanup-aware pattern or a library.

Exercise

  1. Find any useEffect you wrote in earlier lessons that only sets state from other state. Replace it with a calculation.
  2. Build a product list with a search box. Filter the list during render. Verify there is no filteredProducts state.
  3. Make a "new message" form that clears itself when you switch between two conversations, using a key.
  4. Change a "like" button that calls onLike from an effect so it calls it from the click handler instead.

Key points

  • Do not use an effect to compute values from props or state; calculate them during rendering instead.
  • Do not use an effect to respond to a user action; put that code in the event handler.
  • To reset a component's state when an id changes, give it a key={id} instead of writing an effect that resets state.
  • Keep effects for synchronising with external systems: network, timers, subscriptions, non-React widgets.

Check yourself

1. A component needs fullName from firstName and lastName state. What is the best way?
2. The user clicks Buy and you must show a notification. Where does that code belong?
3. How can you reset all of a form's state when the user changes from profile 1 to profile 2?
4. Which of these IS a good use of an effect?