useEffect vs useMemo: 5 Key Differences You Need to Understand
Confusing useEffect and useMemo is one of the most common sources of performance issues in React apps. Think of useMemo as a calculator that remembers results, and useEffect as a switchboard operator waiting for external signals before acting.

Trung Vũ Hoàng
Author
1. The Core Purpose of Each Hook
useMemo exists to solve memoization - caching the result of an expensive computation and only recomputing when its dependencies actually change. Its sole goal is to prevent heavy calculations from rerunning unnecessarily after every component re-render.
useEffect is the opposite - it handles side effects: tasks outside a component's render scope such as calling an API, setting up subscriptions, or directly interacting with the DOM. If you put computation logic in useEffect, React has to render again to update state, which is wasteful.
2. Execution Timing: A Critical Difference
useMemo runs during the render phase. When React reads the component code to prepare the UI, it checks the dependency array for useMemo - if nothing changed, the cached value is returned immediately. Therefore, never put side effects inside useMemo, as that would slow down rendering.
useEffect runs only after rendering completes and the browser has painted the UI (commit phase). This design ensures heavy tasks like data fetching do not block the user from seeing the interface - it's intentional to keep the app responsive.
3. When Should You Use useMemo?
Only use useMemo in two clear situations:
Expensive computations: Filtering lists with thousands of items, running complex search algorithms, processing large datasets, etc.
Ensure referential stability: When you return an object or array and pass it to a child component wrapped with
React.memo. Without useMemo, a new object is created on every render, so the child sees "new data" and re-renders unnecessarily.
Do not use useMemo for trivial calculations - the overhead of initializing the hook and comparing dependencies can outweigh the benefit.
4. Practical Uses of useEffect
useEffect is the control center for everything that happens outside the component. Common cases include:
Calling an API when the component mounts or when dependencies change.
Subscribing to a WebSocket to receive real-time data.
Setting up and cleaning up event listeners.
One thing useMemo does not have: a cleanup function. When a component unmounts or before the effect runs again, cleanup lets you cancel in-flight API requests, remove listeners, and free memory to avoid leaks.
Tip: Before using useEffect to update state, ask yourself: "Can I compute this value directly during render?" If yes, use useMemo or compute it inline. No useEffect needed.
5. Summary: 5 Key Differences
Difference 1: Purpose
useMemo: Cache a value ("remember something").useEffect: Handle side effects ("do something").
Difference 2: When it runs
useMemo: During render (before paint).useEffect: After render completes (after paint).
Difference 3: Return value
useMemo: A concrete value (number, string, array, object, etc.).useEffect: undefined or a cleanup function.
Difference 4: Cleanup support
useMemo: None.useEffect: Has a cleanup function on unmount or when dependencies change.
Difference 5: Impact on rendering
useMemo: Avoids extra re-renders by preserving the same value.useEffect: Can trigger additional renders if it updates state inside.
6. Common Mistakes to Avoid
Stale closures: Using variables inside a hook but forgetting to list them in the dependency array. The code reads stale data with no warning - one of the most insidious bugs.
Handling events inside useEffect: When a user clicks "Buy", handle the logic directly in onClick - don't change some intermediate state and wait for useEffect to "catch" it. The direct approach is clearer and easier to debug.
One useEffect doing too much: Follow the Single Responsibility principle - each useEffect should handle one task. This helps React manage re-renders accurately and makes the code easier for teammates to read.
Conclusion
The best thing you can do is keep components as simple as possible. Make it work first, then measure with a profiler to decide whether memoization is necessary. Don't optimize prematurely based on intuition.
Frequently Asked Questions
Bài viết liên quan

Zustand Async: 5 Effective Ways to Handle Async in React
Every real-world React app needs to communicate with async APIs. If handled poorly, it’s easy to run into issues like frozen UI, race conditions, memory leaks, or showing stale data. Zustand solves this with an extremely simple syntax—define async actions directly in the store, without complex middleware like Redux Thunk or Saga.

Advanced Promises: all, allSettled, race, any — When to Use Which?
If you only use sequential async/await, your app is wasting performance potential — each request has to wait for the previous one to finish before it can start. Promise’s static methods (all, allSettled, race, any) let you orchestrate multiple async tasks in parallel using the strategy that fits each problem.

Zustand Async: 5 Effective Ways to Handle Async Operations in React
Every real-world React app has to communicate with asynchronous APIs. If managed poorly, your app can run into issues like a frozen UI, race conditions, memory leaks, or showing stale data. Zustand solves this with an incredibly simple syntax — define async actions directly in the store, without complex middleware like Redux Thunk or Saga.