Summary
A React component derived state from another state variable immediately after calling its setter, causing stale reads and out‑of‑sync UI behavior. Because React batches and defers state updates, the component attempted to filter products before the new value was applied, resulting in filtered always lagging behind.
Root Cause
The issue stems from reading state synchronously after calling its setter, which does not work in React because state updates are asynchronous and batched.
Key factors:
setProducts(data)schedules an update but does not immediately changeproductssetFiltered(products.filter(...))runs using the oldproductsvalue- The effect runs only once (
[]), sofilterednever recomputes whenproductschanges
Why This Happens in Real Systems
Real React applications frequently hit this problem because:
- State setters do not update immediately
- Effects run after render, not during
- Derived state is often computed too early
- Developers assume synchronous behavior, especially when coming from other frameworks
Real-World Impact
This pattern can cause:
- UI showing outdated or empty lists
- Race conditions when fetching and transforming data
- Hard‑to‑debug inconsistencies between displayed data and actual state
- Extra re-renders when trying to “fix” the issue with unnecessary state
Example or Code (if necessary and relevant)
A correct pattern is to derive filtered after products updates, using a separate effect:
import { useEffect, useState } from "react";
export default function Products() {
const [products, setProducts] = useState([]);
const [filtered, setFiltered] = useState([]);
useEffect(() => {
fetch("/api/products")
.then(res => res.json())
.then(data => setProducts(data));
}, []);
useEffect(() => {
setFiltered(products.filter(p => p.inStock));
}, [products]);
return (
{filtered.map(p => (
- {p.name}
))}
);
}
How Senior Engineers Fix It
Experienced engineers avoid stale state by:
- Deriving state inside a dedicated effect that depends on the source state
- Avoiding redundant state entirely when possible
- Using memoization (
useMemo) for derived values - Keeping state minimal and single‑sourced
Common senior patterns:
- Compute derived values from state, not store them
- Use effects only when side effects are required
- Treat state setters as asynchronous signals, not immediate mutations
Why Juniors Miss It
Less experienced developers often:
- Assume
setStateworks like a synchronous variable assignment - Don’t yet understand React’s render cycle
- Forget that effects run after render, not during
- Try to store derived values instead of computing them
- Don’t recognize that duplicated state leads to drift
The core misunderstanding is believing that React updates state immediately, when in reality it schedules updates and re-renders later.