Suggest an editImprove this articleRefine the answer for “How useMemo works and why is it needed”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)`useMemo` caches a computed value between React re-renders and recalculates only when dependencies change. ```jsx const filtered = useMemo( () => items.filter(item => item.active), [items] // recalculate only when items changes ); ``` **Key point:** use it for expensive operations (heavy filter or sort) or when you need stable object references for child components.Shown above the full answer for quick recall.Answer (EN)Image**useMemo** is a React hook that caches a computed value between re-renders, recalculating it only when specified dependencies change. ## Theory ### TL;DR - Think of a chef who preps an expensive sauce once per shift and reuses it unless key ingredients change. - Without `useMemo`, every render runs the calculation again, even if inputs are identical. - React stores the result and compares deps with `Object.is`. If nothing changed, it returns the cached value. - Use it when compute takes more than ~5ms and deps are stable across most renders. - Functions go to `useCallback`, values go to `useMemo`. ### Quick example ```jsx import { useMemo, useState } from 'react'; const TodoList = ({ todos }) => { const [filter, setFilter] = useState('all'); // Without useMemo: filters on EVERY render, even if filter didn't change // const visible = todos.filter(t => t.status === filter); const visible = useMemo( () => todos.filter(t => t.status === filter), [todos, filter] // only recompute when these change ); return <ul>{visible.map(t => <li key={t.id}>{t.text}</li>)}</ul>; }; ``` On a parent re-render where `filter` and `todos` haven't changed, `useMemo` returns the cached array. No filtering happens. ### Key difference Without `useMemo`, expensive functions run on every render regardless of whether inputs changed. That alone is a CPU cost. But there's a second problem: every render produces a new object or array reference, which breaks referential equality for child components and triggers their re-renders too. `useMemo` stores the previous result in the component's fiber node, compares each dep with `Object.is`, and skips the callback when nothing changed. One call saves both the compute and the downstream re-renders. ### When to use - Heavy filter, sort, or reduce on large arrays (1,000+ items) - `useMemo`. - Derived state that stays stable across most renders - `useMemo`. - Object or array passed as a prop to a `React.memo` component - `useMemo`. - Computation under 1ms - just inline the expression. - Deps that change on every render - skip it, the cache will never hit. ### How it works internally React stores each `useMemo` result in a `memoizedState` linked list on the component's fiber node. On first render it runs `mountMemo` and saves both the value and the deps array. On re-render it runs `updateMemo`: it loops through the deps with `Object.is` (this is `areHookInputsEqual` in the React source), and if every dep matches, returns the cached value without calling the callback. If any dep changed, it calls the callback, stores the new result, and continues. Two things worth knowing. First, `useMemo` runs synchronously during the render phase, so async callbacks don't work inside it. Second, the cache is not permanent. React can discard it under memory pressure or when the fiber is thrown away on unmount. It's an optimization hint, not guaranteed storage. ### useMemo vs useCallback | | `useMemo` | `useCallback` | |---|---|---| | Returns | A value | A function | | Use case | Computed data, objects, arrays | Event handlers, callbacks | | Example | `useMemo(() => a + b, [a, b])` | `useCallback(() => doX(), [x])` | | When to skip | Cheap compute | Callback never passed to children | ### Common mistakes **Unstable object in deps** ```jsx // Recomputes every render - { key: 'value' } is a new ref each time const value = useMemo(() => heavyCalc(items), [items, { key: 'value' }]); // Fix: use the primitive directly const value = useMemo(() => heavyCalc(items), [items, config.key]); ``` **Memoizing cheap operations** ```jsx // Hook overhead is bigger than the savings here const doubled = useMemo(() => a * 2, [a]); // Just inline it const doubled = a * 2; ``` **Mutating the memoized value** ```jsx const list = useMemo(() => items.filter(isActive), [items]); list.push(newItem); // Stales the cache - React assumes the callback is pure // Fix: create a new array const withNew = [...list, newItem]; ``` **Missing deps** ```jsx // userId changes but useMemo never recomputes because deps is [] const result = useMemo(() => fetchData(userId), []); // Fix: add userId to deps const result = useMemo(() => fetchData(userId), [userId]); ``` **Nested useMemo cascade** ```jsx const data1 = useMemo(() => heavy1(input), [input]); const data2 = useMemo(() => heavy2(data1), [data1]); // data1 is new -> data2 always recomputes // Fix: combine into one memo for the whole pipeline const { data1, data2 } = useMemo(() => { const d1 = heavy1(input); return { data1: d1, data2: heavy2(d1) }; }, [input]); ``` In production I've seen this cascade pattern tank performance in dashboards that chain 4-5 derived states. Combining them into one `useMemo` cut re-render time by 60% in those cases. ### Real-world usage - TanStack Query memoizes query results so stable data never triggers downstream renders. - Redux Toolkit's `createSelector` (from Reselect) implements the same idea as `useMemo` but at the store level. - Next.js uses memoized derived metadata during `getStaticProps` processing. - `React.memo` + `useMemo` is the standard combo: `React.memo` skips the child component re-render, `useMemo` keeps the props referentially stable so the skip actually fires. ### Follow-up questions **Q:** What is the time complexity of dep comparison in `useMemo`? **A:** O(n) where n is the number of deps. React loops through each one with `Object.is`. Keep the deps array short, ideally under 5 entries. **Q:** Does `useMemo` block rendering? **A:** Yes. The callback runs synchronously during the render phase. If the computation is slow enough to block the UI, `useMemo` alone won't fix it - you need to move the work off the main thread. **Q:** What's the difference between `useMemo` and `React.memo`? **A:** `useMemo` caches a value inside a single component. `React.memo` wraps a component and skips its re-render if props haven't changed. They solve different problems but work well together. **Q:** What happens if a dep is a `Date` object? **A:** `Object.is` compares by reference. A `new Date()` call every render creates a new instance, so the comparison always fails and `useMemo` always recomputes. Use a primitive like a timestamp number or ISO string as the dep instead. **Q:** (Senior) How does concurrent mode affect the `useMemo` cache? **A:** In React 18, low-priority work can be interrupted and fibers can be discarded. When a fiber is dropped, its `memoizedState` is lost. If the work restarts, `useMemo` recomputes from scratch. For non-urgent memos, wrap the state update in `startTransition` so React knows it can deprioritize the work without causing stale data issues. ## Examples ### Basic: filtering a product list ```jsx import { useMemo, useState } from 'react'; const ProductList = ({ products }) => { const [search, setSearch] = useState(''); const filtered = useMemo( () => products.filter(p => p.name.toLowerCase().includes(search.toLowerCase())), [products, search] ); return ( <> <input value={search} onChange={e => setSearch(e.target.value)} /> <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul> </> ); }; ``` `filtered` only recalculates when `products` or `search` changes. Typing in the input triggers a recalculate. A parent re-render with the same props does not. ### Intermediate: computing stats in a todo app ```jsx const TodoStats = ({ todos }) => { const { activeCount, completedCount } = useMemo(() => { let active = 0; let completed = 0; todos.forEach(todo => { if (todo.completed) completed++; else active++; }); return { activeCount: active, completedCount: completed }; }, [todos]); return <p>{activeCount} active, {completedCount} completed</p>; }; ``` The object returned from `useMemo` is the same reference across re-renders as long as `todos` doesn't change. That matters when `TodoStats` is wrapped in `React.memo`, because a new object reference every render would break the skip. ### Advanced: unstable object deps ```jsx // This breaks - config is a new object on every parent render const BadChart = ({ data, config }) => { const processed = useMemo( () => data.map(item => ({ ...item, color: config.theme })), [data, config] // config !== prev config even if theme didn't change ); return <Chart data={processed} />; }; // Fix - destructure the primitive you actually care about const GoodChart = ({ data, config }) => { const { theme } = config; const processed = useMemo( () => data.map(item => ({ ...item, color: theme })), [data, theme] // primitive comparison works correctly ); return <Chart data={processed} />; }; ``` This edge case trips a lot of experienced devs. The linter won't always catch it because `config` is technically used inside the callback. The fix is to destructure at the top and put only the primitives in the deps array.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.