| name | react-useeffect-avoid |
| description | Guides when NOT to use useEffect and suggests better alternatives. Use when reviewing React code, troubleshooting performance, or considering useEffect for derived state or form resets. |
React: When Not to Use useEffect
Core Principle
useEffect is an escape hatch for synchronizing with external systems, not a general-purpose tool for state management or event handling.
Modern React patterns prioritize one-way data flow
and event-driven updates over effect-based synchronization to avoid performance penalties
and complex synchronization bugs.
When NOT to Use useEffect
1. ❌ Calculating Derived State
Problem: Computing one state value from another using useEffect causes unnecessary
double-renders and complexity.
function FilteredList({ items }) {
const [query, setQuery] = useState('');
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(items.filter(item => item.name.includes(query)));
}, [items, query]);
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
✅ Solution: Calculate derived state directly during render.
function FilteredList({ items }) {
const [query, setQuery] = useState('');
const filtered = items.filter(item => item.name.includes(query));
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
Why it's better:
- Immediate feedback UI updates (no double render pause)
- Simpler mental model: what you see is what you get
- No dependency array to manage
- React can optimize calculations via memoization when needed
2. ❌ Resetting State When a Prop Changes
Problem: Watching a prop change and manually resetting state causes stale data display
and extra renders.
function UserForm({ userId }) {
const [userName, setUserName] = useState('');
const [email, setEmail] = useState('');
useEffect(() => {
setUserName('');
setEmail('');
}, [userId]);
return (
<form>
<input value={userName} onChange={e => setUserName(e.target.value)} />
<input value={email} onChange={e => setEmail(e.target.value)} />
</form>
);
}
✅ Solution A: Key Prop (Preferred)
function App() {
const [userId, setUserId] = useState(1);
return (
<div>
<button onClick={() => setUserId(prev => prev + 1)}>Next User</button>
{/* Different key = different component instance */}
<UserForm key={userId} userId={userId} />
</div>
);
}
✅ Solution B: Derived State (When you only need partial reset)
function UserForm({ userId }) {
const [userName, setUserName] = useSyncExternalStore(
() => ({ onSet: setUserName }),
{ value: '' }
);
const [email, setEmail] = useState('');
return <form>...</form>;
}
⚠️ Caveat: The key prop approach remounts the entire component.
Use when you need a complete state reset.
For partial resets, consider derived state patterns.
3. ❌ Waterfall of State Updates
Problem: Multiple effects triggering each other create cascading renders that are hard to debug.
function OrderForm() {
const [formData, setFormData] = useState({});
const [validated, setValidated] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setValidated(validateData(formData));
}, [formData]);
useEffect(() => {
setError(validated ? null : 'Invalid');
}, [validated]);
useEffect(() => {
if (!error && validated) submitOrder(formData);
}, [error, validated, formData]);
}
✅ Solution: Handle all logic in event handler.
function OrderForm() {
const [formData, setFormData] = useState({});
const handleSubmit = () => {
if (validateData(formData)) {
submitOrder(formData);
} else {
setError('Invalid form');
}
};
return <button onClick={handleSubmit}>Submit</button>;
}
4. ❌ Fetching Data on User Action
Problem: Using a flag to trigger effects loses user intent and creates fragile code.
function LoginForm() {
const [username, setUsername] = useState('');
const [submitted, setSubmitted] = useState(false);
const handleSubmit = () => {
setSubmitted(true);
};
useEffect(() => {
if (submitted) {
login(username);
}
}, [submitted, username]);
}
✅ Solution: Perform action directly in handler.
function LoginForm() {
const [username, setUsername] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
await login(username);
} catch (error) {
console.error('Login failed:', error);
}
};
return <form onSubmit={handleSubmit}>...</form>;
}
5. ❌ Filtering/Transforming Lists
Problem: Creating filtered lists via effects is a common mistake that causes extra renders.
function ProductList({ products }) {
const [visibleProducts, setVisibleProducts] = useState([]);
useEffect(() => {
setVisibleProducts(products.filter(p => p.inStock));
}, [products]);
}
✅ Solution: Filter directly during render.
function ProductList({ products }) {
const visibleProducts = products.filter(p => p.inStock);
}
6. ❌ Subscribing to Browser APIs (Without useSyncExternalStore)
Problem: Using useState + useEffect
for external subscriptions causes "tearing" in concurrent rendering.
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
}
✅ Solution: Use useSyncExternalStore for external state.
function WindowSize() {
const width = useSyncExternalStore(
(callback) => {
window.addEventListener('resize', callback);
return () => window.removeEventListener('resize', callback);
},
() => window.innerWidth,
() => 1200
);
}
Why it's better:
- React can guarantee UI consistency even during concurrent rendering
- Single source of truth
- Built-in optimization - subscription fires only when needed
7. ❌ Initial Data Fetch Without Dependencies (Stale Data)
Problem: Empty dependency array with state inside effect causes stale bugs.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/user/1`).then(res => res.json()).then(setUser);
}, [ ]);
}
✅ Solution: Include all dependencies or use proper patterns.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/user/${userId}`).then(res => res.json()).then(setUser);
}, [userId]);
}
Better: Move to event-driven fetching or data libraries.
function UserProfile({ userId }) {
const user = use(userId);
}
Decision Tree: useEffect vs Alternatives
Need to sync with external system?
├─ Yes (browser APIs, websockets, timers)
│ └─ Use useEffect
│
└─ No (pure React application logic)
├─ Derived state calculation?
│ ├─ Yes → Calculate during render
│ └─ No → Continue...
│
├─ User action triggered?
│ ├─ Yes → Use event handler
│ └─ No → Continue...
│
├─ State reset needed?
│ ├─ Yes → Use key prop
│ └─ No → Continue...
│
└─ Really need effect after re-think?
└─ Yes → Use useState/useReducer/setState pattern
Patterns to Always Avoid
❌ 1. useEffect for DOM Manipulation (without refs)
function Modal() {
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = ''; };
}, []);
}
✅ Better: Use refs or layout effects when needed.
❌ 2. useEffect for Logging
function App({ data }) {
useEffect(() => {
console.log('Data changed:', data);
}, [data]);
}
✅ Better: Log in event handler or use React DevTools profiling.
❌ 3. useEffect for Component Initialization
function Widget() {
const [initialized, setInitialized] = useState(false);
useEffect(() => {
someLibrary.init();
setInitialized(true);
}, []);
}
✅ Better: Initialize outside component effect:
let initialized = false;
function Widget() {
if (!initialized) {
someLibrary.init();
initialized = true;
}
}
Or use library-specific initialization patterns.
Quick Reference
❌ Don't use useEffect for
| Scenario | Problem | Alternative |
|---|
| Derived state | Double render | Calculate during render |
| State resets | Stale data | Use key prop |
| User actions | Lost intent | Event handlers |
| List filtering | Extra renders | Filter in render |
| Browser APIs | Tearing bugs (concurrent) | useSyncExternalStore |
| Form submission | Fragile flag pattern | Direct async handler |
| Data fetching | Manual cache management | React Query, SWR, Suspense |
✅ DO use useEffect for
- Subscribing to external systems (websockets, browser APIs, etc.)
- Setting up timers with cleanup
- Managing third-party library integration
- Document title changes
- Analytics/telemetry when rendering completes
React 19: New Alternatives
React 19 introduces the use API for reading resources in render:
function UserProfile({ userId }) {
const user = use(fetchUser(userId));
return <div>{user.name}</div>;
}
This eliminates many data-fetching useEffect patterns entirely.
References and Further Reading
Official Documentation
Articles from Senior Engineers
Key Principles
- Effects are escape hatches - use only when stepping outside React
- Event-driven > Effect-driven - prefer handlers for user actions
- Render-time > Effect-time - calculate values during render
- Single source of truth - avoid duplicating state in effects
- Concurrent-safe - use specialized hooks for external subscriptions