How do you pass a callback function in react TypeScript?

A reader of my blog reached me on Facebook with an interesting question. He said his teammates, no matter the situation, were wrapping every callback function inside useCallback():

jsx

import React, { useCallback } from 'react';

function MyComponent() {

const handleClick = useCallback(() => {

// handle the click event

}, []);

return <MyChild onClick={handleClick} />;

}

"Every callback function should be memoized to prevent useless re-rendering of child components that use the callback function" is the reasoning of his teammates.

This reasoning is far from the truth. Such usage of useCallback() without profiling makes the component slower and increases code complexity.

In this post, I'm going to explain how to use correctly useCallback().

1. Understanding functions equality check

Before diving into useCallback() use, let's distinguish the problem useCallback() solves — the functions equality check.

Functions in JavaScript are first-class citizens, meaning that a function is a regular object. The function object can be returned by other functions, be compared, etc.: anything you can do with an object.

Let's write a function

javascript

function factory() {

return (a, b) => a + b;

}

const sumFunc1 = factory();

const sumFunc2 = factory();

console.log(sumFunc1(1, 2)); // => 3

console.log(sumFunc2(1, 2)); // => 3

console.log(sumFunc1 === sumFunc2); // => false

console.log(sumFunc1 === sumFunc1); // => true

3 that returns functions that sum numbers:

javascript

function factory() {

return (a, b) => a + b;

}

const sumFunc1 = factory();

const sumFunc2 = factory();

console.log(sumFunc1(1, 2)); // => 3

console.log(sumFunc2(1, 2)); // => 3

console.log(sumFunc1 === sumFunc2); // => false

console.log(sumFunc1 === sumFunc1); // => true

Try the demo.

javascript

function factory() {

return (a, b) => a + b;

}

const sumFunc1 = factory();

const sumFunc2 = factory();

console.log(sumFunc1(1, 2)); // => 3

console.log(sumFunc2(1, 2)); // => 3

console.log(sumFunc1 === sumFunc2); // => false

console.log(sumFunc1 === sumFunc1); // => true

4 and

javascript

function factory() {

return (a, b) => a + b;

}

const sumFunc1 = factory();

const sumFunc2 = factory();

console.log(sumFunc1(1, 2)); // => 3

console.log(sumFunc2(1, 2)); // => 3

console.log(sumFunc1 === sumFunc2); // => false

console.log(sumFunc1 === sumFunc1); // => true

5 are functions that sum two numbers. They've been created by the

javascript

function factory() {

return (a, b) => a + b;

}

const sumFunc1 = factory();

const sumFunc2 = factory();

console.log(sumFunc1(1, 2)); // => 3

console.log(sumFunc2(1, 2)); // => 3

console.log(sumFunc1 === sumFunc2); // => false

console.log(sumFunc1 === sumFunc1); // => true

3 function.

The functions

javascript

function factory() {

return (a, b) => a + b;

}

const sumFunc1 = factory();

const sumFunc2 = factory();

console.log(sumFunc1(1, 2)); // => 3

console.log(sumFunc2(1, 2)); // => 3

console.log(sumFunc1 === sumFunc2); // => false

console.log(sumFunc1 === sumFunc1); // => true

4 and

javascript

function factory() {

return (a, b) => a + b;

}

const sumFunc1 = factory();

const sumFunc2 = factory();

console.log(sumFunc1(1, 2)); // => 3

console.log(sumFunc2(1, 2)); // => 3

console.log(sumFunc1 === sumFunc2); // => false

console.log(sumFunc1 === sumFunc1); // => true

5 share the same code source, but they are different function objects. Comparing them

javascript

function factory() {

return (a, b) => a + b;

}

const sumFunc1 = factory();

const sumFunc2 = factory();

console.log(sumFunc1(1, 2)); // => 3

console.log(sumFunc2(1, 2)); // => 3

console.log(sumFunc1 === sumFunc2); // => false

console.log(sumFunc1 === sumFunc1); // => true

9 evaluates to

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

0.

That's just how JavaScript objects work. An object (including a function object) only to itself.

2. The purpose of useCallback()

Different function objects sharing the same code are often created inside React components:

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

1 is a different function object on every rendering of

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

2.

Because inline functions are cheap, the re-creation of functions on each rendering is not a problem. A few inline functions per component are acceptable.

But in some cases you need to maintain a single function instance between renderings:

  1. A functional component wrapped inside
  2. When the function object is a dependency to other hooks, e.g.

    jsx

    function MyComponent() {

    // handleClick is re-created on each render

    const handleClick = () => {

    console.log('Clicked!');

    };

    // ...

    }

    3
  3. When the function has some internal state, e.g. when the .

That's when

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

4 is helpful: given the same dependency values

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

5, the hook returns the same function instance between renderings (aka memoization):

jsx

import { useCallback } from 'react';

function MyComponent() {

// handleClick is the same function object

const handleClick = useCallback(() => {

console.log('Clicked!');

}, []);

// ...

}

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

1 variable has always the same callback function object between renderings of

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

2.

3. A good use case

You have a component

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

8 that renders a big list of items:

jsx

import useSearch from './fetch-items';

function MyBigList({ term, onItemClick }) {

const items = useSearch(term);

const map = item => <div onClick={onItemClick}>{item}</div>;

return <div>{items.map(map)}</div>;

}

export default React.memo(MyBigList);

The list could be big, maybe hundreds of items. To prevent useless list re-renderings, you wrap it into

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

9.

The parent component of

jsx

import { useCallback } from 'react';

function MyComponent() {

// handleClick is the same function object

const handleClick = useCallback(() => {

console.log('Clicked!');

}, []);

// ...

}

0 provides a handler function to know when an item is clicked:

jsx

import { useCallback } from 'react';

export function MyParent({ term }) {

const onItemClick = useCallback(event => {

console.log('You clicked ', event.currentTarget);

}, [term]);

return (

<MyBigList

term={term}

onItemClick={onItemClick}

/>

);

}

jsx

import { useCallback } from 'react';

function MyComponent() {

// handleClick is the same function object

const handleClick = useCallback(() => {

console.log('Clicked!');

}, []);

// ...

}

1 callback is memoized by useCallback(). As long as

jsx

import { useCallback } from 'react';

function MyComponent() {

// handleClick is the same function object

const handleClick = useCallback(() => {

console.log('Clicked!');

}, []);

// ...

}

3 is the same, useCallback() returns the same function object.

When

jsx

import { useCallback } from 'react';

function MyComponent() {

// handleClick is the same function object

const handleClick = useCallback(() => {

console.log('Clicked!');

}, []);

// ...

}

5 component re-renders,

jsx

import { useCallback } from 'react';

function MyComponent() {

// handleClick is the same function object

const handleClick = useCallback(() => {

console.log('Clicked!');

}, []);

// ...

}

1 function object remains the same and doesn't break the memoization of

jsx

import { useCallback } from 'react';

function MyComponent() {

// handleClick is the same function object

const handleClick = useCallback(() => {

console.log('Clicked!');

}, []);

// ...

}

0.

That was a good use case of useCallback().

4. A bad use case

Let's look at another example:

jsx

import { useCallback } from 'react';

function MyComponent() {

// Contrived use of `useCallback()`

const handleClick = useCallback(() => {

console.log('You clicked');

}, []);

return <MyChild onClick={handleClick} />;

}

function MyChild ({ onClick }) {

return <button onClick={onClick}>I am a child</button>;

}

The first problem is that useCallback() hook is called every time

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

2 renders. This already reduces the render performance.

The second problem is that using useCallback() increases the complexity of the code. You have to keep the

jsx

function MyComponent() {

// handleClick is re-created on each render

const handleClick = () => {

console.log('Clicked!');

};

// ...

}

5 of

jsx

import useSearch from './fetch-items';

function MyBigList({ term, onItemClick }) {

const items = useSearch(term);

const map = item => <div onClick={onItemClick}>{item}</div>;

return <div>{items.map(map)}</div>;

}

export default React.memo(MyBigList);

3 in sync with what you're using inside the memoized callback.

Does it worth using useCallback()? Most likely not because

jsx

import useSearch from './fetch-items';

function MyBigList({ term, onItemClick }) {

const items = useSearch(term);

const map = item => <div onClick={onItemClick}>{item}</div>;

return <div>{items.map(map)}</div>;

}

export default React.memo(MyBigList);

5 component is light, and its re-rendering doesn't create performance issues. The optimization costs more than not having the optimization.

Simply accept that rendering creates new function objects:

jsx

import { useCallback } from 'react';

function MyComponent() {

const handleClick = () => {

console.log('You clicked');

};

return <MyChild onClick={handleClick} />;

}

function MyChild ({ onClick }) {

return <button onClick={onClick}>I am a child</button>;

}

5. Summary

When thinking about performance tweaks, recall the statement:

Profile before optimizing

When deciding to use an optimization technique, such as memoization with useCallback(), do:

  1. Profile
  2. Quantify the increased performance (e.g.

    jsx

    import useSearch from './fetch-items';

    function MyBigList({ term, onItemClick }) {

    const items = useSearch(term);

    const map = item => <div onClick={onItemClick}>{item}</div>;

    return <div>{items.map(map)}</div>;

    }

    export default React.memo(MyBigList);

    7 vs

    jsx

    import useSearch from './fetch-items';

    function MyBigList({ term, onItemClick }) {

    const items = useSearch(term);

    const map = item => <div onClick={onItemClick}>{item}</div>;

    return <div>{items.map(map)}</div>;

    }

    export default React.memo(MyBigList);

    8 render speed increase)
  3. Ask yourself: does the increased performance, compared to increased complexity, worth using useCallback()?

To enable the memoization of the entire component output I recommend checking my post Use React.memo() wisely.

Do you know use cases that are worth using useCallback()? Please share your experience in a comment below.

Like the post? Please share!

Suggest Improvement

Quality posts into your inbox

I regularly publish posts containing:

  • Important JavaScript concepts explained in simple words
  • Overview of new JavaScript features
  • How to use TypeScript and typing
  • Software design and good coding practices

Subscribe to my newsletter to get them right into your inbox.

Subscribe

Join 6946 other subscribers.

How do you pass a callback function in react TypeScript?

How do you pass a callback function in react TypeScript?

About Dmitri Pavlutin

Tech writer and coach. My daily routine consists of (but not limited to) drinking coffee, coding, writing, coaching, overcoming boredom 😉.

How do you call a callback function in TypeScript?

Callback in Angular2/TypeScript In service class, you can create method which can accept parameter of function type. After method is done processing the request, it will execute the callback function with data that needs to be passed.

How do you pass a callback function in React hook?

The useCallback hook is used when you have a component in which the child is rerendering again and again without need. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.

How do you pass a function as props in React TypeScript?

Create a function in the parent component that accepts a callback as an argument. Pass the function to the child component as a prop. In the child component, create the function you want to pass UP to the parent. Call the parent function prop in the child component with the child function as the argument.

How do you pass a function to another function in TypeScript?

Similar to JavaScript, to pass a function as a parameter in TypeScript, define a function expecting a parameter that will receive the callback function, then trigger the callback function inside the parent function.