3. Memoizing a function definition with useCallback
Memoizing a function definition with useCallback
This blog is part of a series on understanding difference between memo
, useMemo
and useCallback
. click here to check out more blogs.
if you jumped directly to this blog, this codesandbox contains our progress so far.
let's implement delete task feature to understand usage of usecallback
. the first thing we're going to do is implement handleDelete
function in app
component:
// app.tsx
const handleDelete = (id: number) => {
settodolist((prevlist: array<todo>) => {
return prevlist.filter((task) => task.id !== id);
});
};
then passing it to the todolist
component as a prop:
// app.tsx
<todolist todolist={filteredtodolist} handledelete={handleDelete} />
also, update types.ts
file and the props of the TodoList
:
// types.ts
...
export type handleDelete = (id: number) => void;
// TodoList.tsx
import { Todo, handleDelete } from "./Types";
...
type Props = {
todoList: Array<Todo>,
handleDelete: handleDelete,
};
Next, you need to destructure handleDelete
from props and pass it down to the Task
component:
// TodoList.tsx
function TodoList({ todoList, handleDelete }:Props) {
...
<Task key={id} id={id} task={task} handleDelete={handleDelete} />
...
}
In Task
component, we need to update props and add button that calls handleDelete
on onClick
:
import { Todo, handleDelete } from "./Types";
...
type Props = Todo & { handleDelete: handleDelete };
...
function Task({ id, task, handleDelete }: Props) {
...
return (
<li>
<span style={{ marginRight: "0.5rem" }}>{task}</span>
<button onClick={() => handleDelete(id)}>X</button>
</li>
)
}
...
also, enable console.log
in Task
and TodoList
component.
At this point, our application should look like the following and should be able to delete the task.
upon clicking on X
:
everything looks good till now. but again we have little problem try typing something in the input, here "Complete Assignment":
on just typing "Compl" you will see set of un-necessary renders, you might think that 'even after adding HOC memo
, why still these batches of renders are appearing?'. the reason for this is that we have declared handleSearch
function in the App
component and we're passing it from App
to List
and to Task
component, the issue is this function is declared every time whenever component renders. so how do we fix this problem?
as you might have already figured out, using useCallback
, let's see how to do that.
useCallback
hook has very similar syntax to the useMemo
, but the main difference is that useMemo
memoizes result of the function and useCallback
memoizes function definition instead.
useCallback(() => someFunctionDefinition, [dependencies]);
modify handleDelete
like this:
// App.tsx
const handleDelete = useCallback((id: number) => {
setTodoList((prevList: Array<Todo>) => {
return prevList.filter((task: Todo) => {
return task.id !== id;
});
});
}, []);
Now, our app should just fine if we write "Complete Assignment":
as expected, we don't have any extra renders of Task
and List
components.
Memoize function passed as an argument in effect
If you jumped directly to this section, this codesandbox contains our progress so far.
There is a special case in which you will need to use the useCallback
hook. this is when you pass function as argument(dependency) in useEffect
hook, for that add below code to App
component:
// App.tsx
const printTodoList = () => {
console.log("changing todolist");
};
...
useEffect(() => {
printTodoList();
}, [todoList]);
...
since, we're listening for changes in todoList
, we added that as a dependency and also remove all console.log
in the app.
Now, if you check the preview for this code and delete or add a new task:
everything works fine. now add todoList
state variable:
// App.tsx
const printTodoList = () => {
console.log("changing todoList", todoList);
};
You'll get following warning:
Basically, it's asking us to add printTodoList
function to the dependencies:
// App.tsx
...
useEffect(() => {
printTodoList();
}, [todoList, printTodoList]);
...
But now, after we do that, we'll get another warning:
the reason for this warning is useEffect
is going to fire on every render, because printTodoList
is going to re-declared on every render and also we're manipulating state(consoling the state), so we can use useCallback
hook to fix this issue:
const printTodoList = useCallback(() => {
console.log("changing state", todoList);
}, [todoList]);
Now, if add or delete task, it should work fine:
That's a wrap, the final code is here.that's a lot of information to grasp, so let's sum it up:
memo
:
- memoize component
- re-memoize when props change
- avoid re-renders
useMemo
:
- memoize calculated value
- for computed properties
- for heavy processes
useCallback
:
- memoize function definition to avoid redefining it on each render
- use whenever pass function as an effect argument(dependency)
- use it with the function passed as props
and finally remember the golden rule: do not use them until absolute necessary.
Source:
for reference, here is the code we have so far.