To understand the code easier, React implemented unidirectional data flow, also called 'one-way data-binding' when passing data from parent component to the child.
However, often, we need to pass some data back to the parent from the child, for example, think of forms, when the user's input effects parent component.
For the newcomers to React sometimes, it is hard to grasp the patterns of how to send data back to from child. This article explains how to do it the easy way.
Use case
Let's imagine we have a parent element, that renders three child elements. Each child element has a button, and each time the user press it, the parent should show which color in the button was selected.
function Parent() {
return (
<>
<h1>Selected color: </h1> // show selected color
{['green','red','blue'].map((color) => (
<Child color={color} ... />
))}
</>
)
}
function Child({ color }) {
return (
<button value={color}>{color} button</button>
)
}
Passing argument from top to bottom is easy via props, but to send data back might seem tricky.
Callback to the rescue
Let's reverse engineer it from bottom to top:
- To capture the button click event, we need to add a handler
function Child({ color }) {
function handleClick(event) {
// do something meaningful
}
return (
<button name={color} onClick={handleClick}>{color}</button>
)
}
- Inside the handler is the perfect place to call another function, a callback, passed from parent component by props -
onChildClick
. Note that we didn't create or passed it yet, but do it later on. A callback can receive any arguments, and parent component will have access to them. In this case, we will pass an argumentname
from the button.
function Child({ color, onChildClick }) {
function handleClick(event) {
onChildClick(event.target.name); // pass any argument to the callback
}
return (
<button name={color} onClick={handleClick}>{color}</button>
)
}
- The last step will be to read the arguments from the callback and save them to the parent component state for later use.
- Create callback function
handleChildClick
and pass it to the Child component through proponChildClick
. - Add
useState
hook, assign state variablecolor
and a functionsetColor
to update it. - Read an argument from the
handleChildClick
function, passed from the child component, and callsetColor
function to update the state with new value.
- Create callback function
function Parent() {
const [color, setColor] = useState('');
function handleChildClick(color) {
setColor(color);
}
return (
<>
<h1>selected color: {color}</h1>
{['green','red','blue'].map((color) => (
<Child color={color} onChildClick={handleChildClick} ... />
))}
</>
)
}
That's pretty much it, on every button click we call event handler, inside it we call a callback function from props
and inside the callback function (in this case handleChildClick
) setting the state to the parent component.
Where you might struggle
- Calling callback directly from event and passing an argument. It will invoke function instantly for all rendered button elements and will not be functional.
<button onClick={onChildClick(color)} ... />
- Passing argument to event handler will also invoke function instantly for all rendered button elements and will not be functional.
<button onClick={handleClick(color)} ... />
- Using inline arrow function and calling callback inside it will create a new arrow function each time button is rendered, also, you will lose event object if you do not explicitly pass it to the callback. Possible, but not efficient.
<button onClick={(event) => onChildClick(color, event)} ... />
- If using class component and a method as an event handler, do not forget to bind the context. With
bind
all further arguments, likeevent
will be forwarded.
<button onClick={this.handleClick.bind(this, color)} .../>
Summing-up
Passing arguments from child to parent is not that confusing, it just might be a little bit tricky to figure out the right place to call the callbacks. I hope this article will clear some of the confusion.
Top comments (4)
This is great! Very good notes, I've been playing with React and have run into most of the problems you described, so it helps!
I do have a question about doing this between classes, maybe someone can help. I see here your child and parent are functions. I'm using classes, with 'extends React.Component'. When I create my child class's function, which calls the parent class's function, I get "'(parent function)' is not defined". Is there a way for me to get my child function to either return the function (that it does not know about), or recognize the parent function? Thanks if anyone can help!
Example with class:
repl.it/@Ngobteam/React-Event-Son-...
Thank you😁
Thank you so much - helped a lot!