| name | react-effect-patterns |
| description | Guidelines for proper React useEffect usage and avoiding unnecessary Effects. Use when writing, reviewing, or refactoring React components that use useEffect, useState, or handle side effects. Triggers on tasks involving React Effects, derived state, event handlers, data fetching, or component synchronization. |
React Effect Patterns
Effects are an escape hatch from React for synchronizing with external systems. Removing unnecessary Effects makes code easier to follow, faster to run, and less error-prone.
When to Use Effects
- Synchronizing with external systems (non-React widgets, network, browser DOM)
- Analytics on component display
- Data fetching (with cleanup for race conditions)
When NOT to Use Effects
- Transforming data for rendering
- Handling user events
- Updating state based on other state
Decision Tree
Why does this code run?
- Component displayed โ Effect
- User action โ Event handler
- Calculable from props/state โ Calculate during render
Anti-Patterns and Solutions
1. Derived State
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(firstName + " " + lastName);
}, [firstName, lastName]);
const fullName = firstName + " " + lastName;
2. Expensive Calculations
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter],
);
Use console.time()/console.timeEnd() to measure. Memoize if โฅ1ms.
3. Reset State on Prop Change
useEffect(() => {
setComment("");
}, [userId]);
<Profile userId={userId} key={userId} />;
4. Adjust Part of State on Prop Change
useEffect(() => {
setSelection(null);
}, [items]);
const selection = items.find((item) => item.id === selectedId) ?? null;
5. Event-Specific Logic
useEffect(() => {
if (product.isInCart) {
showNotification("Added " + product.name + "!");
}
}, [product]);
function handleBuyClick() {
addToCart(product);
showNotification("Added " + product.name + "!");
}
6. Form Submission
useEffect(() => {
if (jsonToSubmit !== null) {
post("/api/register", jsonToSubmit);
}
}, [jsonToSubmit]);
function handleSubmit(e) {
e.preventDefault();
post("/api/register", { firstName, lastName });
}
7. Effect Chains
useEffect(() => {
setGoldCardCount((c) => c + 1);
}, [card]);
useEffect(() => {
setRound((r) => r + 1);
}, [goldCardCount]);
useEffect(() => {
setIsGameOver(true);
}, [round]);
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
}
}
}
8. App Initialization
useEffect(() => {
loadDataFromLocalStorage();
checkAuthToken();
}, []);
let didInit = false;
function App() {
useEffect(() => {
if (!didInit) {
didInit = true;
loadDataFromLocalStorage();
checkAuthToken();
}
}, []);
}
9. Notify Parent of State Changes
useEffect(() => {
onChange(isOn);
}, [isOn, onChange]);
function updateToggle(nextIsOn) {
setIsOn(nextIsOn);
onChange(nextIsOn);
}
function Toggle({ isOn, onChange }) {
function handleClick() {
onChange(!isOn);
}
}
10. Pass Data to Parent
useEffect(() => {
if (data) onFetched(data);
}, [data]);
function Parent() {
const data = useSomeAPI();
return <Child data={data} />;
}
11. External Store Subscription
useEffect(() => {
const handler = () => setIsOnline(navigator.onLine);
window.addEventListener("online", handler);
window.addEventListener("offline", handler);
return () => {
window.removeEventListener("online", handler);
window.removeEventListener("offline", handler);
};
}, []);
return useSyncExternalStore(
subscribe,
() => navigator.onLine,
() => true,
);
12. Data Fetching with Race Conditions
useEffect(() => {
let ignore = false;
fetchResults(query).then((json) => {
if (!ignore) setResults(json);
});
return () => {
ignore = true;
};
}, [query]);
Quick Reference
| Scenario | Solution |
|---|
| Transform data | Calculate during render |
| Expensive calculation | useMemo |
| Reset all state on prop | key attribute |
| Adjust state on prop | Derive during render |
| Share event logic | Extract function, call from handlers |
| User events | Event handlers |
| External system sync | Effect |
| Notify parent | Update in handler or lift state |
| Init once | Module-level or guard variable |
| External store | useSyncExternalStore |
| Fetch data | Effect with cleanup |