Stop Chaining useEffect: The Right Way to Handle Derived State in React
The Anti-Pattern: Syncing State via Effects
A frequent pitfall in dashboard and list components looks like this:
JavaScript
// ❌ Redundant state + extra render cycle
function SearchableList({ items }) {
const [filter, setFilter] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
useEffect(() => {
setFilteredItems(
items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
);
}, [items, filter]);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>
</div>
);
}
Why this hurts:
The component renders once with stale filteredItems.
The useEffect fires after paint, updating state.
The component re-renders a second time with the filtered result.
You risk race conditions and hard-to-trace infinite loops as props scale.
The Clean Approach: Compute During Rendering
Calculate the value directly inside the component body:
JavaScript
// ✅ Zero redundant state, single render pass
function SearchableList({ items }) {
const [filter, setFilter] = useState('');
// Computed inline on every render
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>
</div>
);
}
When to Bring in useMemo
Don't reach for useMemo preemptively for arrays under a few thousand items—JavaScript filtering is extremely fast. Only wrap the computation if profiling shows measurable lag:
JavaScript
const filteredItems = useMemo(() => {
return items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()));
}, [items, filter]);
Key Takeaways
Never store derived data in state: If a value is computable from existing state or props, calculate it inline.
Cut redundant render passes: Updating state inside a useEffect on prop change forces an unnecessary second render cycle.
Keep effects for synchronization: Reserve useEffect strictly for external APIs, DOM mutations, subscriptions, or WebSocket listeners.
CTA
Where does state management get messy in your current codebase? Drop a snippet of a tricky useEffect or state dependency you're refactoring, and let's optimize it together in the comments!
The Anti-Pattern: Syncing State via Effects
A frequent pitfall in dashboard and list components looks like this:
JavaScript
// ❌ Redundant state + extra render cycle
function SearchableList({ items }) {
const [filter, setFilter] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
useEffect(() => {
setFilteredItems(
items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
);
}, [items, filter]);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>
</div>
);
}
Why this hurts:
The component renders once with stale filteredItems.
The useEffect fires after paint, updating state.
The component re-renders a second time with the filtered result.
You risk race conditions and hard-to-trace infinite loops as props scale.
The Clean Approach: Compute During Rendering
Calculate the value directly inside the component body:
JavaScript
// ✅ Zero redundant state, single render pass
function SearchableList({ items }) {
const [filter, setFilter] = useState('');
// Computed inline on every render
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>
</div>
);
}
When to Bring in useMemo
Don't reach for useMemo preemptively for arrays under a few thousand items—JavaScript filtering is extremely fast. Only wrap the computation if profiling shows measurable lag:
JavaScript
const filteredItems = useMemo(() => {
return items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()));
}, [items, filter]);
Key Takeaways
Never store derived data in state: If a value is computable from existing state or props, calculate it inline.
Cut redundant render passes: Updating state inside a useEffect on prop change forces an unnecessary second render cycle.
Keep effects for synchronization: Reserve useEffect strictly for external APIs, DOM mutations, subscriptions, or WebSocket listeners.
CTA
Where does state management get messy in your current codebase? Drop a snippet of a tricky useEffect or state dependency you're refactoring, and let's optimize it together in the comments!
Stop Chaining useEffect: The Right Way to Handle Derived State in React
The Anti-Pattern: Syncing State via Effects
A frequent pitfall in dashboard and list components looks like this:
JavaScript
// ❌ Redundant state + extra render cycle
function SearchableList({ items }) {
const [filter, setFilter] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
useEffect(() => {
setFilteredItems(
items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
);
}, [items, filter]);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>
</div>
);
}
Why this hurts:
The component renders once with stale filteredItems.
The useEffect fires after paint, updating state.
The component re-renders a second time with the filtered result.
You risk race conditions and hard-to-trace infinite loops as props scale.
The Clean Approach: Compute During Rendering
Calculate the value directly inside the component body:
JavaScript
// ✅ Zero redundant state, single render pass
function SearchableList({ items }) {
const [filter, setFilter] = useState('');
// Computed inline on every render
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>
</div>
);
}
When to Bring in useMemo
Don't reach for useMemo preemptively for arrays under a few thousand items—JavaScript filtering is extremely fast. Only wrap the computation if profiling shows measurable lag:
JavaScript
const filteredItems = useMemo(() => {
return items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()));
}, [items, filter]);
Key Takeaways
Never store derived data in state: If a value is computable from existing state or props, calculate it inline.
Cut redundant render passes: Updating state inside a useEffect on prop change forces an unnecessary second render cycle.
Keep effects for synchronization: Reserve useEffect strictly for external APIs, DOM mutations, subscriptions, or WebSocket listeners.
CTA
Where does state management get messy in your current codebase? Drop a snippet of a tricky useEffect or state dependency you're refactoring, and let's optimize it together in the comments!