Loading...
Loading...
Avoid unnecessary useEffect in React components. Most uses of useEffect are anti-patterns — derived state, event-driven logic, data fetching, and external store subscriptions all have better, more idiomatic alternatives. Apply this skill when writing or reviewing React components that use useEffect.
npx skill4agent add jonmumm/skills dont-use-use-effectuseEffectuseEffectuseEffect// 🔴 BAD: Redundant state + unnecessary Effect
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return <span>{fullName}</span>;
}firstNamelastNamefullName// ✅ GOOD: Calculate during render
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// Derived — no state, no Effect, one render
const fullName = firstName + ' ' + lastName;
return <span>{fullName}</span>;
}useMemo// ✅ GOOD: Memoize expensive derivations
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// Only recomputes when todos or filter change, not when newTodo changes
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);
return <ul>{visibleTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>;
}// 🔴 BAD: Event-specific logic triggered by state change in an Effect
function ProductPage({ product, addToCart }) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the cart!`);
}
}, [product]);
function handleBuyClick() {
addToCart(product);
}
// ...
}// ✅ GOOD: Side effects belong in the event handler that caused them
function ProductPage({ product, addToCart }) {
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to the cart!`);
}
function handleBuyClick() {
buyProduct();
}
function handleCheckoutClick() {
buyProduct();
navigateTo('/checkout');
}
// ...
}// 🔴 BAD: Chain of Effects triggering each other (4 render passes!)
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
}
}, [card]);
useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1);
setGoldCardCount(0);
}
}, [goldCardCount]);
useEffect(() => {
if (round > 5) setIsGameOver(true);
}, [round]);
// ...
}// ✅ GOOD: Derive what you can, compute the rest in the event handler
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
// Derived — no state needed
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
if (isGameOver) throw Error('Game already ended.');
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
if (round === 5) alert('Good game!');
}
}
}
// ...
}useEffectuseEffect// 🔴 BAD: Manual fetch in useEffect — race conditions, no caching, no SSR
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
let ignore = false;
setIsLoading(true);
fetchResults(query).then(json => {
if (!ignore) {
setResults(json);
setIsLoading(false);
}
});
return () => { ignore = true; };
}, [query]);
// ...
}// ✅ GOOD: Use TanStack Query (or your framework's data primitive)
import { useQuery } from '@tanstack/react-query';
function SearchResults({ query }) {
const { data: results = [], isLoading } = useQuery({
queryKey: ['search', query],
queryFn: () => fetchResults(query),
enabled: !!query,
});
// Automatic caching, deduplication, race-condition handling,
// background refetching, and SSR support — for free.
}loaderuse()useSWR(key, fetcher)useEffectignoreuseData(url)useSyncExternalStoreuseEffectsetState// 🔴 BAD: Manual subscription with useEffect
function OnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;
}// ✅ GOOD: useSyncExternalStore — concurrent-safe, less code
import { useSyncExternalStore } from 'react';
function subscribe(callback: () => void) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
function getSnapshot() {
return navigator.onLine;
}
function OnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;
}keyuseEffectkey// 🔴 BAD: Resetting state in an Effect
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}// ✅ GOOD: Use key to reset component state
function ProfilePage({ userId }) {
// When userId changes, React unmounts the old EditProfile and mounts a new one
return <EditProfile userId={userId} key={userId} />;
}
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}useEffect| Use Case | Why It's Correct |
|---|---|
| Integrating a non-React widget (map, chart, video player) | Syncing React state → imperative DOM API |
| Setting up a WebSocket or EventSource connection | External system lifecycle tied to component |
Measuring DOM layout ( | Reading post-render DOM information |
| Managing focus or scroll position imperatively | Imperative DOM interaction |
| Connecting to hardware APIs (camera, geolocation) | External system with subscribe/cleanup |
| Syncing React state → browser API |
react-intersection-observerframer-motionuseEffectuseMemouseSyncExternalStorekeyuseEffectuseEffectuseEffectuseEffectuseEffectuseEffectsetStateuseSyncExternalStoreuseEffectkeyuseEffect