React derived state is the key to faster components. Learn how to stop abusing useEffect for data transformations and simplify your React performance optimization.
During a recent production audit, I found a dashboard component that was re-rendering four times for every single user input. The culprit wasn't complex logic; it was a developer trying to "sync" state using useEffect every time the source data changed.
If you’re reaching for useEffect to transform or filter data before displaying it, you’re likely creating a performance bottleneck. Let’s look at how to fix this by shifting your mental model toward pure calculation during the render phase.
We often fall into the trap of treating useEffect like a lifecycle method from the Class component era. We think: "When props.items changes, I need to update filteredItems." So, we write this:
JAVASCRIPTconst [items, setItems] = useState([]); const [filteredItems, setFilteredItems] = useState([]); useEffect(() => { setFilteredItems(items.filter(item => item.active)); }, [items]);
This is a classic anti-pattern. When items changes, React renders the component, runs the effect, and then triggers another render to update filteredItems. You’ve just doubled the work for the browser. This creates cascading re-renders that make your UI feel sluggish, especially on low-end mobile devices.
Understanding how React rendering: Mastering State Batching and the Two-Pass Model works is crucial here. When you update state inside an effect, you force React to go back to the top of the component tree, wasting cycles on a second pass.
Instead of storing derived data in state, calculate it on the fly. Since your component function re-runs every time props or state change, you can perform the calculation directly in the component body.
JAVASCRIPTfunction ItemList({ items }) { // Calculate directly during render const filteredItems = items.filter(item => item.active); return ( <ul> {filteredItems.map(item => <li key={item.id}>{item.name}</li>)} </ul> ); }
This approach is faster, cleaner, and removes the need for manual synchronization. By leveraging React reconciliation and component state persistence: A mental model, you ensure that your UI always reflects the current source of truth without unnecessary intermediate states.
useMemo?A common follow-up question I get from juniors is: "Won't this slow down the app if the calculation is expensive?"
If you are performing heavy operations—like sorting a list of 5,000 items—then yes, you should wrap the calculation in useMemo. But don't use it prematurely. Start by calculating directly in the body. If your browser performance tools show a drop in frame rate, then optimize.
Remember, useEffect is a synchronization tool, not state management. It’s for talking to the outside world—APIs, DOM manipulation, or subscriptions—not for managing internal data flow.
When you move logic out of useEffect, you reduce the mental overhead of tracking "what updates what." You stop worrying about infinite loops caused by dependency arrays. You also make your components easier to test because they become pure functions of their inputs.
If you're working in a Next.js rendering environment, this becomes even more critical. Server components and client components interact in ways that make "double-render" bugs harder to debug. Keeping your data flow predictable ensures that your hydration phase doesn't produce those dreaded "text content did not match" errors.
useState variable if it’s just a copy of a prop. Just use the prop directly.useEffect for actual side effects, make sure your dependency array is exhaustive. If you're struggling with React State Synchronization: How to Avoid Infinite Loops, it’s almost always a sign that you should be using derived state instead.filter or map in useMemo. Most JavaScript operations are incredibly fast; the cost of the useMemo hook itself can sometimes outweigh the benefit of skipping a simple calculation.Does calculating in the render body make my component slow? Generally, no. React is designed to run the component function repeatedly. Unless your calculation involves thousands of iterations or heavy parsing, you won't notice a difference.
What if I need to reset state when a prop changes?
That is the only valid case for useEffect synchronization. Even then, consider using the key prop on your component to force a full re-mount rather than manually syncing state.
Is useMemo always better than just calculating the value?
No. Only use it when the calculation is genuinely expensive. For simple arrays or strings, the overhead of the hook is often higher than the calculation itself.
I still catch myself writing useEffect for state updates when I'm tired or rushing through a feature. It’s a habit that’s hard to break. The key is to pause and ask: "Is this data derived from existing state?" If the answer is yes, delete the useEffect and calculate it during the render. Your users will appreciate the snappier interface, and your future self will thank you for the simpler code.
React keys are essential for efficient reconciliation. Learn why stable component identity prevents UI bugs and performance bottlenecks when rendering lists.