8 min read • Jul 22, 2026
React optimization with memo, useMemo, and useCallback
Practical guide to shadcn/ui color systems, tokens, hover states, and dark mode design patterns

React memo is an optimization API, often used together with useMemo and useCallback to avoid unnecessary re-renders and recalculations of expensive components.
When parent components re-render, their child components are typically re-rendered as well. In many applications, this is not a problem, since React's rendering process is efficient. However, if a child component performs expensive computations or renders a large subtree, these extra renders can affect performance.
Cases where:
- Component’s props rarely change
- Re-rendering component is expensive
- Component re-renders frequently because of parent re-renders
can be optimized using memo.
memo is a higher-order function that takes a component and returns a new memoized version of it. If its props do not change when the parent component re-renders, React skips calling the component during reconciliation and reuses the previous render.
Consider the example:
const Parent = () => {
const [counter, setCounter] = useState(0);
const name = "Alice";
return (
<div>
<button onClick={() => setCounter(counter + 1)}>
Click to re-render
</button>
<Child name={name} />
</div>
);
};
const Child = ({ name }) => {
console.log("child is rendered");
return <p>Hello {name}</p>;
};When the button is clicked, the parent re-renders, causing the child to re-render as well. If we wrap the child in memo, updating the parent’s state won’t trigger a child re-render unless the props are the same between renders:
const Child = memo(({ name }) => {
console.log("child is rendered");
return <p>Hello {name}</p>;
});When to use
Optimizing with memo is only valuable when the component re-renders often with the same props, and its re-rendering logic is expensive. For example, when user interactions involve small, frequent updates, such as typing, filtering, or sorting large lists.
In other cases, there is no benefit in wrapping a component in memo.
How memo works
memo takes two arguments as parameters: a pure component to memoize and an optional arePropsEqual function. This function is used to provide custom logic for comparing old and new props. If skipped, React will perform a shallow comparison between props.
memo returns a memoized version of the component, meaning its rendering result is cached
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)Pure components
A pure component always produces the same UI for the same props, state, and context, and does not have side effects during rendering.
For React memo to work, the same inputs should result in the same outputs. If a component’s output depends on something other than its props, state, or context - such as the current time or random numbers- skipping a render can produce incorrect results.
Because of this, memoization only works for pure components.
arePropsEqual function
arePropsEqual lets you provide custom logic to compare previous and next props instead of React's default comparison. The function should receive two arguments — the previous props and the next props— and should return a boolean:
trueif the props are considered equal, so React can skip re-rendering.falseif the props are different, so React will re-render the component.
If you don't provide arePropsEqual, React compares each prop using Object.is comparison.
Object.is comparison
By default, React.memo compares the previous and next props using a shallow comparison. Each prop is compared using Object.is method.
Shallow comparison means that if a prop is a reference type (such as an object, array, or function), React compares its reference, not its contents.
So if the parent component declares and passes reference props-say, an object - to the memoized child, when re-rendered, the new reference will be created for the object. Since Object.is compares the objects by reference, the props will never be considered equal, and memoization won’t work:
const Parent = () => {
const [counter, setCounter] = useState(0);
const user = { name: "Alice" };
return (
<div>
<button onClick={() => setCounter(counter + 1)}>
Click to re-render
</button>
<Child user={user} />
</div>
);
};
const Child = memo(function Child({ user }) {
console.log("Child rendered");
return <p>Hello {user.name}</p>;
});Every time Parent re-renders, a new user object is created. Although the object contains the same data, it has a different reference. Since memo performs a shallow comparison of props, it treats the new object as a changed prop, causing Child to re-render.
There are multiple techniques to avoid child re-rendering in similar scenarios, such as destructuring an object and passing the required values as primitive props.
In this example, however, we'll use useMemo to demonstrate how it can memoize an object and preserve its reference between renders.
How useMemo works
useMemo caches the result of an expensive calculation and returns the same reference until its dependencies change.
This means that if the callback returns an object, useMemo preserves the same object reference between renders until its dependencies change.
const user = useMemo(() => ({ name: "Alice" }), []);useMemo accepts two parameters:
calculateValueA function that calculates a value you want to cache. The function must be pure, should take no arguments, and can return a value of any type.- An array of dependencies
useMemo returns the result of calling calculateValue with no arguments.
Consider example:
Suppose fruits is a massive array; typing in the input filters the array, and we pass filtered results as props to the child for rendering:
const Parent = () => {
const [counter, setCounter] = useState(0);
const [search, setSearch] = useState('');
const results = fruits.filter((fruit) =>
fruit.toLowerCase().includes(search.toLowerCase())
);
return (
<div>
<button onClick={() => setCounter(counter + 1)}>
Click to re-render
</button>
<input value={search} onChange={(e) => setSearch(e.target.value)} />
<Child results={results} />
</div>
);
};
const Child = memo(function Child({ results }) {
console.log("Child rendered");
return (
<ul>
{results.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
});Clicking the counter button will cause the component to re-render. When the Parent re-renders, it will recalculate filtered results, even though we haven’t typed anything in the input. And since the results are an array, when re-rendered, its reference will change, causing the child component to re-render as well.
We can wrap results in useMemo and cache the filtered results:
const Parent = () => {
const [counter, setCounter] = useState(0);
const [search, setSearch] = useState("");
const results = useMemo(() => {
return fruits.filter((fruit) =>
fruit.toLowerCase().includes(search.toLowerCase())
);
}, [search]);
return (
<div>
<button onClick={() => setCounter(counter + 1)}>
Click to re-render
</button>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<Child results={results} />
</div>
);
};
const Child = memo(function Child({ results }) {
console.log("Child rendered");
return (
<ul>
{results.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
});Now, if we press the button, since dependencies are the same, the filtered results will be cached, avoiding both redundant operations: recalculating results and re-rendering the child component.
Memoizing function references with useCallback
Like objects, functions are reference types. Each time a function () {} or () => {} expression is evaluated, JavaScript creates a new function with a different reference.
So the same is true for functions: if we pass a function as a prop to a memoized child, re-rendering the parent will make the child re-render as well, and the memoization won’t work. This is where useCallback is useful.
useCallback caches a function definition between re-renders.
By wrapping the function in useCallback, React returns the same function reference across renders as long as its dependencies don't change. This allows React.memo to see that the function prop is unchanged and skip re-rendering the child component.
How useCallback works
useCallback takes two arguments: a function to memoize and a dependency array.
The function can take any arguments and return any value. On the initial render, useCallback returns the function you passed. On subsequent renders, it returns the cached function as long as the dependencies remain unchanged. If a dependency changes, it returns and caches the new function.
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);Dependencies is an array of all values referenced in the passed function. React compares whether dependencies change with Object.is comparison.
Say we pass a function to a child component:
const Parent = () => {
const [name, setName] = useState("");
const handleClick = () => {
console.log("clicked");
};
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<Child onClick={handleClick} />
</div>
);
};
const Child = memo(function Child({ onClick }) {
console.log("Child rendered");
return <button onClick={onClick}>Click</button>;
});Every time we type in the input, the Parent re-renders, creating a new handleClick function. Creating a new handleClick function changes the child props, causing it to re-render.
We can memoize the handleClick function by wrapping it in the useCallback hook:
const Parent = () => {
const [name, setName] = useState("");
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<Child onClick={handleClick} />
</div>
);
};
const Child = memo(function Child({ onClick }) {
console.log("Child rendered");
return <button onClick={onClick}>Click</button>;
});Like other cached examples, the memoized function won’t be recreated unless the dependencies change, avoiding unnecessary re-renders.
Summary
React.memo is used to skip re-rendering expensive components that often re-render because of their parent. It takes a pure component to memoize and returns a new memoized version of it.
React.memoonly compares props. If the component's own state changes, it will still re-render.- If a prop is an object, array, or function that is recreated on every parent render,
React.memowill see it as changed because the reference is different. In such cases,useMemooruseCallbackmay be needed to keep prop references stable.useMemois a React hook, that takes a function and memoizes the result of that function.useCallback, also React hook, memoizes the function it takes.
- For memoization to work, passed functions should be pure.
- Wrapping every component in
React.memocan actually add unnecessary comparison overhead.