Don't use useEffect to sync state that can be computed from existing state or props. Derived values should be calculated during render, not in effects. Effects for derived state cause extra renders, race conditions, and stale UI that flash incorrect data before correcting.
useEffect for derived state causes a double-render on every change: first the component renders with stale derived state, then the effect fires and triggers a second render with the correct value. Users see a flash of wrong data, and in concurrent mode the stale render can interleave with other updates, producing race conditions that are nearly impossible to reproduce.
BeforeMerge scans your pull requests against this rule and 4+ others. Get actionable feedback before code ships.
When you store a value in state that could be computed from other state or props, and then use useEffect to keep it "in sync," you introduce a subtle but damaging pattern. The component renders once with the stale derived value, then the effect fires and sets state again, causing a second render.
This double-render is not just a performance issue. During the first render, your UI shows incorrect data. In React 18's concurrent mode, these stale intermediate renders can interleave with other updates, producing race conditions that are extremely difficult to debug.
The fix is simple: compute derived values directly during render. React is designed for this — it's just a function call.
If a value can be computed from existing state or props, calculate it during render instead of storing it in state and syncing with useEffect. Use useMemo only when the computation is genuinely expensive (measurable with profiling).
function ProductList({ products, searchQuery }: Props) {
const [filteredProducts, setFilteredProducts] = useState(products);
// BAD: useEffect to "sync" derived state
useEffect(() => {
setFilteredProducts(
products.filter((p) => p.name.toLowerCase().includes(searchQuery.toLowerCase()))
);
}, [products, searchQuery]);
return (
<ul>
{filteredProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}function ProductList({ products, searchQuery }: Props) {
// GOOD: compute during render — no extra state, no effect, no double-render
const filteredProducts = products.filter((p) =>
p.name.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
<ul>
{filteredProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}Look for the pattern: useState + useEffect where the effect calls the setter with a value computed from props or other state:
grep -n "useEffect" --include="*.tsx" --include="*.ts" -r src/Then inspect each effect: if the only thing it does is call a state setter with a value derived from dependencies, it is a violation.
useState for the derived valueuseEffect that synced ituseMemo with the same dependency array