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:
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;Full name: Asha RaoIt 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:
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;Full name: Asha RaoThe 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:
// 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:
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;Buy
Thanks for your order!
CONSOLE
POST /api/orderA 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:
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:
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;Next user
Profile 2
0 characters typed
Showing the box for user 2I 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
// 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:
// 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:
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.logor 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
- Find any
useEffectyou wrote in earlier lessons that only sets state from other state. Replace it with a calculation. - Build a product list with a search box. Filter the list during render. Verify there is no
filteredProductsstate. - Make a "new message" form that clears itself when you switch between two conversations, using a
key. - Change a "like" button that calls
onLikefrom 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.