Debounce
is a function that waits for a certain amount of time before executing a function.
Debouncing is a strategy used to improve the performance
of a feature by controlling the time at which a function should be executed.
Debouncing accepts a function and transforms it in to an updated (debounced) function so that the code inside the original function is executed after a certain period of time.If the debounced function is called again within that period, the previous timer is reset and a new timer is started for this function call. The process repeats for each function call.
- Delay the function execution by a certain time,
delay
. - Reset the timer if the function is called again.
To debounce a function, we'll have a separate function that accepts the function reference and the delay as parameters, and returns a debounced function.
function debounce(func, delay) {
return () => {} // return debounced function
}
This function will only be called once to return a debounced function and that, in turn, will be used in the subsequent code.
function debounce(func, delay) {
return () => {
setTimeout(() => {
func()
}, delay)
}
}
This delays
the function call by delay milliseconds. But this is incomplete as it only satisfies the first requirement. How do we achieve the second behaviour?
Let's create a variable timeout
and assign it to the return value of setTimeout
method. The setTimeout
method returns a unique identifier to the timeout
, which is held by timeout
variable.
function debounce(func, delay) {
let timeout=null
return () => {
timeout=setTimeout(() => {
func()
}, delay)
}
}
Each time you invoke setTimeout
, the ID is different. We will use this timeout
variable to reset the timer.
But how do we get access to timeout
from outside the debounce() method? As mentioned before, debounce() is only used once to return a debounced function. This, in turn, performs the debouncing logic.
Then, how does the debounced function have access to timeout
even if it is used outside the debounce() function? Well, it uses a concept called closure.
useMemo
is a React Hook that lets you cache the result of a calculation between re-renders.
const cachedValue = useMemo(calculateValue, dependencies)
Call useMemo
at the top level of your component to cache a calculation between re-renders:
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
calculateValue
: The function calculating the value that you want to cache. It should be pure, should take no arguments, and should return a value of any type. React will call your function during the initial render. On next renders, React will return the same value again if thedependencies
have not changed since the last render. Otherwise, it will callcalculateValue
, return its result, and store it so it can be reused later.dependencies
: The list of all reactive values referenced inside of thecalculateValue
code. Reactive values include props, state, and all the variables and functions declared directly inside your component body.
- On the initial render,
useMemo
returns the result of callingcalculateValue
with no arguments. During next renders, it will either return an already stored value from the last render (if the dependencies haven’t changed), or callcalculateValue
again, and return the result thatcalculateValue
has returned.
To cache a calculation between re-renders, wrap it in a useMemo
call at the top level of your component:
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
You need to pass two things to useMemo
:
- A
calculation function
that takes no arguments, like() =>
, and returns what you wanted to calculate. - A
list of dependencies
including every value within your component that’s used inside your calculation.
In some cases, useMemo
can also help you optimize performance of re-rendering child components. To illustrate this, let’s say this TodoList
component passes the visibleTodos
as a prop to the child List
component:
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
You’ve noticed that toggling the theme
prop freezes the app for a moment, but if you remove <List />
from your JSX, it feels fast. This tells you that it’s worth trying to optimize the List
component.
By default, when a component re-renders, React re-renders all of its children recursively. This is why, when TodoList
re-renders with a different theme
, the List
component also re-renders. This is fine for components that don’t require much calculation to re-render. But if you’ve verified that a re-render is slow, you can tell List
to skip re-rendering when its props are the same as on last render by wrapping it in memo
:
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
With this change, List
will skip re-rendering if all of its props are the same as on the last render. This is where caching the calculation becomes important! Imagine that you calculated visibleTodos
without useMemo
:
export default function TodoList({ todos, tab, theme }) {
// Every time the theme changes, this will be a different array...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... so List's props will never be the same, and it will re-render every time */}
<List items={visibleTodos} />
</div>
);
}
In the above example, the filterTodos
function always creates a different array, similar to how the {} object literal always creates a new object. Normally, this wouldn’t be a problem, but it means that List props will never be the same, and your memo optimization won’t work. This is where useMemo comes in handy:
export default function TodoList({ todos, tab, theme }) {
// Tell React to cache your calculation between re-renders...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...so as long as these dependencies don't change...
);
return (
<div className={theme}>
{/* ...List will receive the same props and can skip re-rendering */}
<List items={visibleTodos} />
</div>
);
}
By wrapping the visibleTodos
calculation in useMemo
, you ensure that it has the same value between the re-renders (until dependencies change). You don’t have to wrap a calculation in useMemo
unless you do it for some specific reason. In this example, the reason is that you pass it to a component wrapped in memo
, and this lets it skip re-rendering. There are a few other reasons to add useMemo
which are described further on this page.