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.


After graduation, my career is entirely centered around learning and improving as a developer. I’ve began working full time as a React developer and I’ll be blogging about everything that I encounter and learn during this journey. This will range from improving communicational skills in a technical environment, becoming a better developer, improving technical skills in React and JavaScript, and discussing career related topics. In all of my posts, the focus will be on my personal experiences, learnings, difficulties, solutions (if present), and also flaws.

If you’re either interested in these topics, more personalised technical stories, or the perspective of a learning developer, you can follow me either on Twitter or at Dev to stay up to date with my blogposts. I’m always learning, so stay tuned for more stories! 🎉