How to Pass Function as Props in React Functional Component
Jasser Mark Arioste
Hello! In this tutorial, you'll learn how to pass a function as props in a react functional component.
Introduction #
We usually want to pass functions in react as handlers or callbacks for an event or user input. However, if you're new to React, you might be wondering why the onClick()
callback doesn't work, there are a few possible reasons and we'll discuss them in this tutorial.
Incorrect way of Passing a Function as Props #
Usually, it's straightforward to pass functions as props in a react functional component. We only have to pass a function. For example:
const ComponentA = () => { const handleClick = () => { console.log(`Button is clicked`); }; return <div> <button onClick={handleClick}>My Button</button> </div>; }; export default ComponentA;
12345678910
However, one of the most common mistakes is passing functions with parameters. Beginners usually call/execute the callback function and pass the arguments with it which is incorrect. This is especially true if you're not using typescript. For example:
const ComponentA = () => { const handleClick = (name: string) => { console.log(`Button ${name} is clicked`); }; return <div> <button onClick={handleClick("my-button")}>My Button</button> </div>; }; export default ComponentA;
12345678910
In this example, we want to log the name of the button. But the problem is, we are already executing/calling the function which returns void
. Using typescript will result in a type error: Type 'void' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined'.
There are two ways to fix this:
- Wrapping the call in an anonymous function.
- Modify handleClick so that it returns a function instead of void.
Solution 1: Wrapping the call in an anonymous function
Oftentimes, it is sufficient to wrap the handler in an anonymous function. To wrap the call in an anonymous function, we can just do the following.
const ComponentA = () => { const handleClick = (name: string) => { console.log(`Button ${name} is clicked`); }; return ( <div> <button onClick={() => { handleClick("my-button"); }} > My Button </button> </div> ); }; export default ComponentA;
123456789101112131415161718
Solution 2: Modifying handleClick
so that it returns a function
This is also an option but I don't recommend it since it makes the code more unreadable than the previous solution. For example:
const ComponentA = () => { const handleClick = (name: string) => { return function () { return console.log(`Button ${name} is clicked`); }; }; return ( <div> <button onClick={handleClick("my-button")}>My Button</button> </div> ); }; export default ComponentA;
1234567891011121314
Passing Function when Iterating an Array #
Another scenario is when using Array.map
to iterate some array of objects and pass a function event handler. For example, suppose we have a <TodoList/>
component that lists an array of todo's and once we click the delete button, it should remove that specific Todo
.
Incorrect way
import { useState } from "react"; const TodoList = () => { const [todos, setTodos] = useState([ { id: 1, description: "Do Exercise", completed: false, }, { id: 2, description: "Cook food", completed: false, }, ]); //delete handler const handleDelete = (id: number) => { setTodos(todos.filter((todo) => todo.id !== id)); }; return ( <div> TodoList <div> {/* inside Array.map */} {todos.map((todo) => { return ( <div key={todo.id}> <p> {todo.description} - <button onClick={handleDelete(todo.id)}>Delete</button> {/*Incorrect since handleDelete is executed */} </p> </div> ); })} </div> </div> ); }; export default TodoList;
12345678910111213141516171819202122232425262728293031323334353637383940
In Line 31, this is incorrect since handleDelete
is executed which returns void. To fix this we have to wrap it in a function.
Correct way
<div key={todo.id}> <p> {todo.description} - <button onClick={()=>handleDelete(todo.id)}>Delete</button> </p> </div>
123456
Passing a Function in useCallback
#
Sometimes, we need to use the useCallback
hook to avoid re-creating the function every time the component re-renders. To implement this we just have to wrap our existing function in a useCallback
hook. We can also preserve its parameters.
import { useCallback, useState } from "react"; const TodoList = () => { const [todos, setTodos] = useState([ { id: 1, description: "Do Exercise", completed: false, }, { id: 2, description: "Cook food", completed: false, }, ]); const handleDelete = useCallback( (id: number) => { setTodos(todos.filter((todo) => todo.id !== id)); }, [todos] ); return ( <div> TodoList <div> {/* inside Array.map */} {todos.map((todo) => { return ( <div key={todo.id}> <p> {todo.description} - <button onClick={() => handleDelete(todo.id)}>Delete</button> </p> </div> ); })} </div> </div> ); }; export default TodoList;
12345678910111213141516171819202122232425262728293031323334353637383940414243
As you can see, we didn't change anything when we used the function in line 34. This improves performance if you have a lot of states in this component that can trigger a re-render.
Conclusion #
You learned how to pass functions as props in a react functional component. You learned what not to do, and what to do in different scenarios when passing functions in event handlers.
If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.
Resources #
Credits: Image by Frank Winkler from Pixabay