useMemo
React HookEditor’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
.
Jump ahead:
useMemo
Hook?useMemo
useMemo
as an escape hatch for the ESLint Hook warningsuseMemo
vs. useRef
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.
useMemo
useMemo
if your operation is inexpensiveConsider 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
.
useMemo
if you are memoizing a defaultState
objectConsider 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
.
useMemo
as an escape hatch for the ESLint Hook warningsWhile I couldn’t bring myself to read all the comments from people who seek ways to suppress the lint warnings from the official ESLint plugin for Hooks, I do understand their plight.
I agree with Dan Abramov on this one. 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.
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:
useMemo
to memoize it. As a general rule, if a computation takes less than a few milliseconds to complete, it’s probably not worth memoizinguseMemo
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 itIn 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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
15 Replies to "When not to use the <code>useMemo</code> React Hook"
Thanks for this useful article. I like the approach of showing each of the possible mis-uses. It would be interesting to get your take on the question, answer and following comments on: https://stackoverflow.com/questions/56028913/usememo-vs-useeffect-usestate?rq=1
Any advice how to solve the following problem: you have a component, props has a list (say list of products) with a colour attribute. You render a list of products in a div simply:
“`
const MyComp(props) =>
{ props.products.map(product=>
{product.name}
}
“`
Say product.color will have just a few values. How can I avoid the creation of a ton of style objects?
That first snippet is wrong, nothing is returned from useMemo
in the last example with the Bla function you do create the array and pass it to the useref function every single time, the way to do it is useState. if its a constant you need to create it outside of the function component or if it is being created on the first render you could use setState inside a useEffect with an emptry array as a dependency and this was it will NOT create the array every single time!
First solution that comes to my mind is something like styled components which support style properties for components, if you don’t want to use that then you can just use classes for product div.
This is completely okay. With useRef, the value passed in is the initialValue. It isn’t recomputed on re-render. It’s ignored.
That’s correct. Should be this:
“`
const resolvedValue = useMemo(() => {
return getResolvedValue(page, type)
}, [page, type])
“`
Yeah. with useRef the first value passed is an initialiser. It is ignored on subsequent re-renders – so it’s totally fine to create a new Array there. It’s ignored on re-renders.
How about the situation where useRef needs to be set with an initial value which is complex to compute? Let’s say it’s a class member constructing which takes memory and time – so why do I need to create the new one on each rerender if I only need the very first one?
why to use `useRef` when you can copy `[1,2,3]` array outside of the component?
Hi! Thanks for your nice article.
Can I translate this article into Korean and upload to my blog?
Of course I’m gonna write the source. 🙂
Hi @youjin, LogRocket editor here. For now, our policy is that we do not approve translations on third-party sites. We appreciate the support, though.
Many of these posts are not correct – “You may rely on useMemo as a performance optimization, not as a semantic guarantee.” is directly in the post. You’re suggesting the use of useMemo, like the places you’re attempting to stop re-triggers on prop changes (mounting an attribution event or something), as a semantic guarantee.
Why we can’t just create a const outside on component and use inside component.
It seems like your final example breaks the idea of a pure function (see the “Pitfall” warning at https://react.dev/reference/react/useRef#examples-value). What are your thoughts on this issue?