
When not to use the useMemo React Hook
When not to use the useMemo React Hook êŽë š

Editorâs note
This article was last updated on 10 March 2023. To read more on React Hooks, check out this cheat sheet.

Despite its usefulness in optimizing React performance, Iâve observed that some developers have a tendency to employ the useMemo
Hook excessively. To delve deeper into this issue, I decided do some code explorations to determine scenarios where the use of useMemo
may not be beneficial.
In this article, weâll explore scenarios in which you might be overusing useMemo
.
What is the useMemo
Hook?
The useMemo
Hook in React is a performance optimization tool that allows you to memoize expensive computations and avoid unnecessary re-renders. When you use useMemo
, you can calculate the value of a variable or function once and reuse it across multiple renders, rather than recalculating it every time your component re-renders.
This can significantly improve the performance of your application, particularly if you have complex or time-consuming computations that need to be done in your components.
Itâs important to note that you should only use useMemo
when you have expensive computations that need to be memoized. Using it for every value in your component can actually hurt performance, as useMemo
itself has a small overhead.
Letâs take at some examples and scenarios in which you should reconsider using the useMemo
Hook in a React app.
When not to use useMemo
Donât use useMemo
if your operation is inexpensive
Consider the example component below:
/**
* @param {number} page
* @param {string} type
**/
const myComponent({page, type}) {
const resolvedValue = useMemo(() => {
return getResolvedValue(page, type)
}, [page, type])
return <ExpensiveComponent resolvedValue={resolvedValue}/>
}
In this example, itâs easy to justify the authorâs use of useMemo
. What goes through their mind is they donât want the ExpensiveComponent
to be re-rendered when the reference to resolvedValue
changes.
While thatâs a valid concern, there are two questions to ask to justify the use of useMemo
at any given time.
First, is the function passed into useMemo
an expensive one? In this case, is the getResolvedValue
computation an expensive one?
Most methods on JavaScript data types are optimized, e.g. Array.map
, Object.getOwnPropertyNames()
, etc. If youâre performing an operation thatâs not expensive (think Big O notation), then you donât need to memoize the return value. The cost of using useMemo
may outweigh the cost of reevaluating the function.
Second, given the same input values, does the reference to the memoized value change? For example, in the code block above, given the page
as 2
and type
as "GET"
, does the reference to resolvedValue
change?
The simple answer is to consider the data type of the resolvedValue
variable. If resolvedValue
is primitive
(i.e., string
, number
, boolean
, null
, undefined
, or symbol
), then the reference never changes. By implication, the ExpensiveComponent
wonât be re-rendered.
Consider the revised code below:
/**
* @param {number} page
* @param {string} type
**/
const MyComponent({page, type}) {
const resolvedValue = getResolvedValue(page, type)
return <ExpensiveComponent resolvedValue={resolvedValue}/>
}
Following the explanation above, if resolvedValue
returns a string or other primitive value, and getResolvedValue
isnât an expensive operation, then this is perfectly correct and performant code.
As long as page
and type
are the same â i.e., no prop changes â resolvedValue
will hold the same reference except the returned value isnât a primitive (e.g., an object or array).
Remember the two questions: Is the function being memoized an expensive one, and is the returned value a primitive? With these questions, you can always evaluate your use of useMemo
.
Donât use useMemo
if you are memoizing a defaultState
object
Consider the following code block:
/**
* @param {number} page
* @param {string} type
**/
const myComponent({page, type}) {
const defaultState = useMemo(() => ({
fetched: someOperationValue(),
type: type
}), [type])
const [state, setState] = useState(defaultState);
return <ExpensiveComponent />
}
The code above seems harmless to some, but the useMemo
call there is unnecessary.
First of all, the intent here is to have a new defaultState
object when the type
prop changes, and not have any reference to the defaultState
object be invalidated on every re-render.
While these are decent concerns, the approach is wrong and violates a fundamental principle: useState
will not be reinitialized on every re-render, only when the component is remounted.
The argument passed to useState
is better called INITIAL_STATE
. Itâs only computed (or triggered) once when the component is initially mounted:
useState(INITIAL_STATE)
Although in the code block above, we are interested in getting a new defaultState
value when the type
array dependency for useMemo
changes, this is a wrong judgment as useState
ignores the newly computed defaultState
object.
This is the same for lazily initializing useState
as shown below:
/**
* @param {number} page
* @param {string} type
**/
const myComponent({page, type}) {
// default state initializer
const defaultState = () => {
console.log("default state computed")
return {
fetched: someOperationValue(),
type: type
}
}
const [state, setState] = useState(defaultState);
return <ExpensiveComponent />
}
In the example above, the defaultState
init function will only be invoked once â on mount. The function isnât invoked on every re-render. As a result, the log âdefault state computedâ will only be seen once, except when the component is remounted.
Hereâs the previous code rewritten:
/**
* @param {number} page
* @param {string} type
**/
const myComponent({page, type}) {
const defaultState = () => ({
fetched: someOperationValue(),
type,
})
const [state, setState] = useState(defaultState);
// if you really need to update state based on prop change,
// do so here
// pseudo code - if(previousProp !== prop){setState(newStateValue)}
return <ExpensiveComponent />
}
We will now consider more subtle scenarios where you should avoid useMemo
.
Using useMemo
as an escape hatch for the ESLint Hook warnings

While I couldnât bring myself to read all the comments (cebook/create-react-app
) from people who seek ways to suppress the lint warnings from the official ESLint plugin for Hooks (eslint-plugin-react-hooks
), I do understand their plight.
I agree with Dan Abramov on this one (facebook/create-react-app
). Suppressing the eslint-warnings
from the plugin will likely come back to bite you someday in the future.
Generally, I consider it a bad idea to suppress these warnings in production apps because you increase the likelihood of introducing subtle bugs in the near future.
With that being said, there are still some valid cases for wanting to suppress these lint warnings. Below is an example Iâve run into myself. The codeâs been simplified for easier comprehension:
function Example ({ impressionTracker, propA, propB, propC }) {
useEffect(() => {
// đTrack initial impression
impressionTracker(propA, propB, propC)
}, [])
return <BeautifulComponent propA={propA} propB={propB} propC={propC} />
}
This is a rather tricky problem.
In this specific use case, you donât care whether the props change or not. Youâre only interested in invoking the track
function with whatever the initial props are. Thatâs how impression tracking works. You only call the impression track function when the component mounts. The difference here is you need to call the function with some initial props.
While you may think simply renaming the props
to something like initialProps
solves the problem, that wonât work. This is because BeautifulComponent
relies on receiving updated prop values, too:

In this example, you will get the lint warning message:
React Hook useEffect has missing dependencies: 'impressionTracker', 'propA', 'propB', and 'propC'. Either include them or remove the dependency array.
Thatâs a rather brash message, but the linter is simply doing its job. The easy solution is to use a eslint-disable
comment, but this isnât always the best solution because you could introduce bugs within the same useEffect
call in the future:
useEffect(() => {
impressionTracker(propA, propB, propC)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
My suggestion solution is to use the useRef
Hook to keep a reference to the initial prop values you donât need updated:
function Example({impressionTracker, propA, propB, propC}) {
// keep reference to the initial values
const initialTrackingValues = useRef({
tracker: impressionTracker,
params: {
propA,
propB,
propC,
}
})
// track impression
useEffect(() => {
const { tracker, params } = initialTrackingValues.current;
tracker(params)
}, []) // you get NO eslint warnings for tracker or params
return <BeautifulComponent propA={propA} propB={propB} propC={propC} />
}
In all my tests, the linter only respects useRef
for such cases. With useRef
, the linter understands that the referenced values wonât change and so you donât get any warnings! Not even useMemo
prevents these warnings.
For example:
function Example({impressionTracker, propA, propB, propC}) {
// useMemo to memoize the value i.e so it doesn't change
const initialTrackingValues = useMemo({
tracker: impressionTracker,
params: {
propA,
propB,
propC,
}
}, []) // đ you get a lint warning here
// track impression
useEffect(() => {
const { tracker, params} = initialTrackingValues
tracker(params)
}, [tracker, params]) // đ you must put these dependencies here
return <BeautifulComponent propA={propA} propB={propB} propC={propC} />
}
In the faulty solution above, even though I keep track of the initial values by memoizing the initial prop values with useMemo
, the linter still yells at me. Within the useEffect
call, the memoized values tracker
and params
still have to be entered as array dependencies, too.
Iâve seen people useMemo
in this way. Itâs poor code and should be avoided. Use the useRef
Hook, as shown in the initial solution.
In conclusion, in most legitimate cases where I really want to silent the lint warnings, Iâve found useRef
to be a perfect ally. Embrace it.
useMemo
vs. useRef
Most people say to use useMemo
for expensive calculations and for keeping referential equalities. I agree with the first but disagree with the second. Donât use the useMemo
Hook just for referential equalities. Thereâs only one reason to do this â which I discuss later.
Why is using useMemo
solely for referential equalities a bad thing? Isnât this what everyone else preaches?
Consider the following contrived example:
function Bla() {
const baz = useMemo(() => [1, 2, 3], [])
return <Foo baz={baz} />
}
In the component Bla
, the value baz
is memoized NOT because the evaluation of the array [1,2,3]
is expensive, but because the reference to the baz
variable changes on every re-render.
While this doesnât seem to be a problem, I donât believe useMemo
is the right Hook to use here.
One, look at the array dependency:
useMemo(() => [1, 2, 3], [])
Here, an empty array is passed to the useMemo
Hook. By implication, the value [1,2,3]
is only computed once â when the component mounts.
So, we know two things: the value being memoized is not an expensive calculation, and it is not recomputed after mount.
If you find yourself in such a situation, I ask that you rethink the use of the useMemo
Hook. Youâre memoizing a value that is not an expensive calculation and isnât recomputed at any point in time. Thereâs no way this fits the definition of the term âmemoization.â
This is a terrible use of the useMemo
Hook. It is semantically wrong and arguably costs you more in terms of memory allocation and performance.
So, what should you do?
First, what exactly is the author of the code trying to accomplish here? They arenât trying to memoize a value; rather, they want to keep the reference to a value the same across re-renders.
In these cases, use the useRef
Hook.
For example, if you donât like the usage of the current property, then simply deconstruct and rename as shown below:
function Bla() {
const { current: baz } = useRef([1, 2, 3])
return <Foo baz={baz} />
}
Problem solved.
In fact, you can use the useRef
to keep reference to an expensive function evaluation â so long as the function doesnât need to be recomputed on props change.
useRef
is the right Hook for such scenarios, NOT the useMemo
Hook.
Being able to use the useRef
Hook to mimic instance variables is one of the least used super powers Hooks avail us. The useRef
Hook can do more than just keeping references to DOM nodes. Embrace it.
Please remember, the condition here is if youâre memoizing a value just because you need to keep a consistent reference to it. If you need the value to be recomputed based off of a changing prop or value, then please feel free to use the useMemo
Hook. In some cases, you can still use useRef
â but useMemo
is mostly convenient given the array dependency list.
Conclusion
In this article, we see that while the useMemo
Hook is useful for performance optimizations in React apps, there are indeed scenarios in which the hook is not needed. Some of these scenarios are:
- When the computation is not expensive: If a computation is relatively cheap to perform, it may not be worth using
useMemo
to memoize it. As a general rule, if a computation takes less than a few milliseconds to complete, itâs probably not worth memoizing - When the computation depends on props that change frequently. If a computation depends on props that change frequently, it may not be worth memoizing
- When the memoized value is not used frequently. If a memoized value is only used in one or two places in your component, it may not be worth the overhead of using
useMemo
to memoize it. In this scenario, it may be more efficient to simply recalculate the value when itâs needed, rather than maintaining a memoized version of it
In general, itâs important to use useMemo
judiciously and only when itâs likely to provide a measurable performance benefit. If youâre not sure whether to use useMemo, itâs a good idea to profile your application and measure the performance impact of different optimizations before making a decision.
