
Optimising Performance in React with useMemo and useCallback

Performance issues in React often come from unnecessary re‑renders and expensive computations. Each time a component re‑renders, React recreates functions and recalculates values, even if they have not changed. This is usually fine for small applications, but as complexity grows, unnecessary re‑renders can slow things down.
React provides useMemo and useCallback, two hooks designed to optimise rendering efficiency. They work by memoising values and functions, ensuring React only recalculates them when necessary. In this article, I explore how these hooks work, when to use them, and when they are unnecessary.
Understanding Component Re‑Renders in React
Each time a component's state or props change, React re‑renders it. Normally, this is not a problem, but if the component contains expensive calculations or function creations, performance can suffer.
Consider this example:
const ExampleComponent = ({ count }: { count: number }) => { console.log("Component re-rendered"); const computeExpensiveValue = () => { console.log("Expensive computation running"); return count * 100; }; return <p>Result: {computeExpensiveValue()}</p>;};Every time ExampleComponent re‑renders, computeExpensiveValue runs again, even if count has not changed. This is where useMemo can help.
Improving Performance with useMemo
What Is useMemo?
useMemo memoises a computed value, meaning it only recalculates when its dependencies change. This is useful for expensive calculations that do not need to run on every render.
Example: Preventing Unnecessary Computations
We can optimise the previous example by using useMemo, like this:
const ExampleComponent = ({ count }: { count: number }) => { console.log("Component re-rendered"); const computedValue = useMemo(() => { console.log("Expensive computation running"); return count * 100; }, [count]); return <p>Result: {computedValue}</p>;};Now, computedValue only recalculates when count changes. If the component re‑renders for another reason, it uses the cached value instead.
When to Use useMemo
useMemo is useful when:
- A calculation is expensive and should not run on every render.
- The result only needs to change when specific values update.
- A large dataset needs filtering, sorting, or processing.
However, overusing useMemo can make code harder to read, so it should only be used when performance is a concern.
Preventing Unnecessary Function Creations with useCallback
What Is useCallback?
useCallback memoises functions, ensuring they are not recreated on every render. This is particularly useful when passing callbacks to child components, as it prevents unnecessary re‑renders.
Example: Preventing Function Recreation
Consider this simple counter component:
const Counter = () => { const [count, setCount] = useState(0); const increment = () => { console.log("Function recreated"); setCount((prev) => prev + 1); }; return <button onClick={increment}>Increment</button>;};Each time the component re‑renders increment is recreated as a new function instance. This is fine in small components, but in larger applications, the cumulative effect can cause performance issues.
We can optimise this using useCallback, like this:
const Counter = () => { const [count, setCount] = useState(0); const increment = useCallback(() => { console.log("Function retained"); setCount((prev) => prev + 1); }, []); return <button onClick={increment}>Increment</button>;};Now, increment only gets created once, improving performance when passing it to child components.
When to Use useCallback
useCallback is useful when:
- Passing a function as a prop to a child component that uses
React.memo. - Preventing functions from being recreated unnecessarily on each render.
- Avoiding unintended re‑renders in components that depend on function references.
Combining useMemo and useCallback
It's pretty common to see these hooks used together, particularly when working with lists, filtering, and memoised components.
Example: Optimising a List Filter
Imagine filtering a list of names based on user input:
const NameList = ({ query }: { query: string }) => { const names = ["Alice", "Bob", "Charlie", "David"]; const filteredNames = useMemo(() => { console.log("Filtering names"); return names.filter((name) => name.toLowerCase().includes(query.toLowerCase())); }, [query]); return ( <ul> {filteredNames.map((name) => ( <li key={name}>{name}</li> ))} </ul> );};Using useMemo, filtering only runs when query changes, avoiding unnecessary recalculations.
Now, let's add a button in the parent component to reset the query:
const ParentComponent = () => { const [query, setQuery] = useState(""); const resetQuery = useCallback(() => { setQuery(""); }, []); return ( <> <input value={query} onChange={(e) => setQuery(e.target.value)} /> <button onClick={resetQuery}>Reset</button> <NameList query={query} /> </> );};By using useCallback, resetQuery remains the same function across renders, preventing unnecessary re‑renders in child components.
When Not to Use useMemo and useCallback
Whilst these hooks can improve performance, they are not always necessary.
Avoid using them when:
- The calculation is not expensive and runs quickly anyway.
- The function is not passed as a prop to a child component.
- The component does not re‑render often enough to justify memoisation.
React's default rendering behaviour is usually efficient, so these hooks should only be used when performance becomes an issue.
Wrapping up
React's useMemo and useCallback help improve performance by reducing unnecessary recalculations and function recreations. useMemo caches computed values whilst useCallback ensures that functions remain stable across renders. When used appropriately, they can help applications run more efficiently without adding unnecessary complexity.
Key Takeaways
useMemocaches expensive calculations, preventing them from running on every render.useCallbackprevents functions from being recreated, which can help optimise performance in memoised components.- Both hooks should only be used when performance issues arise, as unnecessary use can make code harder to maintain.
- React's default rendering behaviour is usually efficient, and these hooks should only be used for optimisations when needed.
When used in the right places, these hooks help keep React applications running smoothly without adding unnecessary complexity.
Related Articles

Solving the LeetCode 'Binary Tree Zigzag Level Order Traversal' Problem. 
Why We Use an Empty Dependency Array in React's useEffect Hook. Why We Use an Empty Dependency Array in React's
useEffectHook
Five Tips for Transitioning from Permanent to Freelancing. Five Tips for Transitioning from Permanent to Freelancing

Best Practices for Vue Router in Large Applications. Best Practices for Vue Router in Large Applications

Some of the Most‑Misunderstood Properties in CSS. Some of the Most‑Misunderstood Properties in CSS

A Beginner's Guide to Web Hosting. A Beginner's Guide to Web Hosting

Understanding prototype.apply() in JavaScript. Understanding
prototype.apply()in JavaScript
Object.assign() in JavaScript: Merging and Shallow Copies. Object.assign()in JavaScript: Merging and Shallow Copies
Toggle a Boolean in JavaScript. Toggle a Boolean in JavaScript

Check If Your Site is Running on localhost. Check If Your Site is Running on
localhost
Backtracking Decision Trees: Solving 'Combination Sum'. Backtracking Decision Trees: Solving 'Combination Sum'

JavaScript's typeof Operator: Uses and Limitations. JavaScript's
typeofOperator: Uses and Limitations