How To Type React useCallback Using TypeScript
May 22, 2022 - 4 minutes
One of the most used hooks in React development is useCallback
. The hook allows developers to easily implement memoization of callbacks, which can then be used for various performance optimization reasons. Its most prominent use case is to avoid unnecessary re-renders of components.
Due to its extensive usage and the popularity of TypeScript, people will inevitably come across the task to type the useCallback
usages using TypeScript. However, there are various ways to do this, each with its own different advantages and drawbacks. This article covers those different approaches together with their characteristics and potential issues.
Before we dive into the different options, let’s take a look at what a typical useCallback
invocation would look like and one of the reasons we want proper typing onto it. A common use case for the useCallback
hook is creating an event handler that is stable across re-renders unless its dependencies change.
However, we also want to access the event of the interaction inside the callback. Unfortunately, without proper typings, TypeScript doesn’t know the type of the callback parameter. As a result, it will default to the any
type. However, this means that we’ll have no useful typing information about the event
variable during development.
This is one of the reasons that we want proper typing on our useCallback
calls. While this reason will be the main driver in this article for exploring different approaches to type useCallback
calls, there are also other reasons. Think of alignment of the return type or the function type itself, which are relevant when the callback is used as a prop value.
import { useCallback } from "react";
import { doSomething } from "./utils";
const ComponentA = ({ content, depA, depB }: ComponentProps) => {
const handleClick = useCallback(
(event) => {
doSomething(depA, depB, event);
},
[depA, depB]
);
return <div onClick={handleClick}>{content}</div>;
};
Type The Callback Parameter
The most straightforward way to get typing for the parameters of the callback is by putting types directly onto them. This can be accomplished by telling directly TypeScript what the type of the argument is, which in this case would be MouseEvent<HTMLElement>
.
import { MouseEvent, MouseEventHandler, useCallback } from "react";
import { doSomething } from "./utils";
const ComponentB = ({ content, depA, depB }: ComponentProps) => {
const handleClick = useCallback(
(event: MouseEvent<HTMLElement>) => {
doSomething(depA, depB, event);
},
[depA, depB]
);
return <div onClick={handleClick}>{content}</div>;
};
While this is a short-term solution to add typing to the function callback parameter, it isn’t really a scalable solution. If any parameters are added in the future, it would require additional typings for all of them. On top of that, doing it this way doesn’t introduce typing onto the callback as a whole. As a result, TypeScript isn’t able to properly verify the typing of the callback against the prop’s type that it’s used for.
Type The Returning Callback
Instead of only typing the function parameters individually, we could type the returning callback with the appropriate typing. This prevents the drawbacks of the previous method as putting a type on the callback covers any changes in the function typing definition and has a type to verify against the prop’s type that it’s used for.
The only major downside to this solution is that it requires a variable to store the useCallback
value. Without it, there’s no way to cast the typing onto the callback. This can be problematic in cases where the hook is used inline or as a return value for a custom hook.
import { MouseEvent, MouseEventHandler, useCallback } from "react";
import { doSomething } from "./utils";
const ComponentD = ({ content, depA, depB }: ComponentProps) => {
const handleClick: MouseEventHandler<HTMLElement> = useCallback(
(event) => {
doSomething(depA, depB, event);
},
[depA, depB]
);
return <div onClick={handleClick}>{content}</div>;
};
Type The useCallback Call
A very similar result can be achieved by a different and arguably slightly cleaner way is to type the useCallback
call. Typings wise, I haven’t found a difference yet in the way TypeScript handles this approach compared to the previous one, where we put the typing onto the return value. However, this approach does address all the inconveniences of the previous approach.
Personally, I find this approach more readable and structured as the useCallback
call is bundled closer with its typing. There is no type casting involved, integrates smoothly with the natural reading flow, has all the typing benefits, and avoids all the inconveniences of the other approaches.
import { MouseEvent, MouseEventHandler, useCallback } from "react";
import { doSomething } from "./utils";
const ComponentC = ({ content, depA, depB }: ComponentProps) => {
const handleClick = useCallback<MouseEventHandler<HTMLElement>>(
(event) => {
doSomething(depA, depB, event);
},
[depA, depB]
);
return <div onClick={handleClick}>{content}</div>;
};
Final Thoughts
Every React developer will know of the useCallback
hook and have tried to integrate it with TypeScript. However, there are various ways to do so. This article covered three different ways, namely by typing only the callback parameter(s), typing the returning value, or the call itself. Each of them builds on top of the characteristics, potential issues, and inconveniences of the previous ones.