[React][Hook] Optimize Performance with Correct useEffect and useMemo
Stop stuffing derived data into useEffect. Learn when useMemo runs during render vs when useEffect runs after paint - and fix flicker and extra re-renders.

1. Environment
- OS: Windows 11 / macOS
- Frontend library: React.js (v18+)
- Core concept: React Hooks (
useEffect,useMemo) - IDE: Visual Studio Code
2. Problem
While building React UIs, a common habit is: “Whenever this state or data changes, I need to update something” - and then every bit of that logic ends up inside useEffect.
Using the wrong Hook for the job often causes a brief empty/flickering screen before data appears, and unnecessary re-renders that hurt performance.
3. Cause
The root issue is misunderstanding when each Hook runs in React?’s rendering (drawing) cycle.
useEffect(after render): Runs asynchronously in the background after the component has painted (after mount / update).useMemo(during render): Runs synchronously while the component is rendering, finishes the calculation before paint, and feeds the result straight into the UI.
| useMemo | useEffect | |
|---|---|---|
| Main purpose | Compute optimization (cache a value) | Side effects (sync with systems outside React) |
| Result | Returns a computed value you assign to a variable | Returns nothing - the goal is to run work |
| Timing | Synchronous (waits for the result) | Asynchronous (does not block paint) |
| Typical uses | Heavy filtering, sorting, expensive math | API calls, timers, direct DOM work |
4. Solution
1) Anti-pattern: using an action tool (useEffect) to produce a value
useEffect does not return a result. It means: “After the screen is drawn, do this.|–Using it to derive display data is inefficient.
const [visibleUsers, setVisibleUsers] = useState([]);
useEffect(() => {
// 1. The screen paints once (visibleUsers is still [])
// 2. After paint, useEffect runs and filters the list
// 3. setVisibleUsers forces a state update
// 4. React re-renders the whole component again (wasted work)
setVisibleUsers(users.filter((user) => user.active));
}, [users]);
// Empty UI first, then a flicker when filtered results appear
return (
<div>
{visibleUsers.map((u) => (
<p key={u.id}>{u.name}</p>
))}
</div>
);
2) Correct pattern: derive the value with useMemo
Use useMemo when you need the result of a calculation assigned to a variable and shown immediately.
const visibleUsers = useMemo(() => {
// 1. Compute while the component is rendering and return the value
// 2. No extra state update is required
return users.filter((user) => user.active);
}, [users]);
// One paint with filtered data - no double render, no flicker
return (
<div>
{visibleUsers.map((u) => (
<p key={u.id}>{u.name}</p>
))}
</div>
);
5. Summary
We compared a common anti-pattern - deriving display data in useEffect“with the correct approach using useMemo.
Drop the habit of reaching for useEffect on every state change. Pick the Hook that matches the goal and you get cleaner render performance.
- Reach for
useEffectwhen: fetching from an API, attaching scroll listeners, running timers - anything that talks to the outside world (network, browser). - Reach for
useMemowhen: sorting, conditional filtering, heavy stats - anything that transforms data so the UI can show it immediately.

