React Child to Parent Communication
Table of Contents + −
In the last lesson, React Parent to Child Communication, you saw data flow down through props. Now we flip it: how does a child send something back up when data only flows down?
🤔 Why do we need this?
On a real screen, the thing that owns the data and the thing the user clicks are often two different components.
- The parent owns the data. Like a list of tasks, or a count.
- The child is the small piece the user touches. Like an input box, or a button.
- When the user clicks, the new value needs to reach the parent so the parent can update its data.
- But props only go down. The child cannot reach up and change the parent’s data directly. That would break one-way data flow.
So we need a clean, allowed way for the child to say “hey, something happened, here’s the value”. That’s what callback props are for.
🧠 The idea: pass a function down as a prop
Here’s the whole trick in one line. A prop doesn’t have to be text or a number. It can be a function too.
- The parent writes a function that knows how to update the parent’s own data.
- The parent passes that function down to the child as a prop, just like any other prop.
- The child doesn’t change anything itself. It just calls the function it was given.
- When the child calls it, the parent’s function runs, and the parent updates its own state.
So the data flows down, the function flows down too. But calling that function lets the child send a value back up, and the parent stays in charge the whole time.
🧩 The naming convention
React has a clear habit for naming here, and following it makes your code easy to read.
- The prop that holds the function starts with
on. LikeonAddItem,onIncrement,onDelete. It describes the event. - The actual function in the parent starts with
handle. LikehandleAddItem,handleIncrement,handleDelete. It describes the work being done.
Here’s the shape so you can see how the two names connect.
// In the parent: the function is named handle...function handleAddItem(value) { /* update parent state */ }
// passed down as a prop named on...<TaskInput onAddItem={handleAddItem} />So you read it like a sentence. The child says onAddItem, the parent answers with handleAddItem. Same idea, two roles.
💡 A simple example: child button updates parent count
Let’s start with the smallest case. The parent keeps a count, and a child button should make it go up.
import { useState } from "react";
// Child: it does NOT own the count. It just calls what it was given.function PlusButton({ onIncrement }) { return <button onClick={onIncrement}>Add one</button>;}
// Parent: owns the count and the function that changes itfunction Counter() { const [count, setCount] = useState(0);
function handleIncrement() { setCount(count + 1); }
return ( <div> <p>Count: {count}</p> <PlusButton onIncrement={handleIncrement} /> </div> );}Here’s what happens when the user clicks, step by step.
- The parent has
handleIncrement, which callssetCountto update the parent’s own state. - The parent passes it down as
onIncrement={handleIncrement}. - The child’s button runs
onIncrementwhen it’s clicked. The child doesn’t know or care what that function does. - That call runs
handleIncrementback in the parent, the count goes up, and React re-renders. The new number shows on screen.
Output
Count: 0 (then after one click)Count: 1 (then after another click)Count: 2🖥️ A more real example: child sends a value up
A button click is fine, but most of the time the child has actual data to send, like text the user typed. So this time the child calls the function with an argument.
import { useState } from "react";
// Child: holds the input text, sends it up when the form is submittedfunction TaskInput({ onAddTask }) { const [text, setText] = useState("");
function handleSubmit(e) { e.preventDefault(); // stop the page from reloading onAddTask(text); // send the value UP to the parent setText(""); // clear the box }
return ( <form onSubmit={handleSubmit}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button type="submit">Add task</button> </form> );}
// Parent: owns the list and decides what to do with the new taskfunction TaskApp() { const [tasks, setTasks] = useState([]);
function handleAddTask(newTask) { setTasks([...tasks, newTask]); // add it to the parent's list }
return ( <div> <TaskInput onAddTask={handleAddTask} /> <ul> {tasks.map((task, i) => <li key={i}>{task}</li>)} </ul> </div> );}Same pattern as before, but now it carries a value.
- The child keeps its own little input text in state, so the box works while typing.
- On submit, the child calls
onAddTask(text). That’s the value going up. - Up in the parent,
handleAddTaskreceives it asnewTaskand adds it to the parent’s list. - The list lives in the parent, so the parent re-renders and the new task appears.
The child decides when, the parent decides what
Notice the split. The child decides WHEN to send the value (on submit) and WHAT value to send. The parent decides WHAT TO DO with it (add it to the list). Neither one reaches into the other’s state.
⚠️ The common mistake: changing parent data directly
This is the trap. When a child needs to update the parent, beginners often try to grab the parent’s data through props and change it. That does not work, and React will not let it.
function PlusButton({ count }) { // ❌ Wrong - a child cannot change a prop, props are read-only function handleClick() { count = count + 1; // does nothing useful, breaks data flow } return <button onClick={handleClick}>Add one</button>;}
// ✅ Right - the parent passes a function, the child just calls itfunction PlusButton({ onIncrement }) { return <button onClick={onIncrement}>Add one</button>;}Let’s be clear about why the first one fails.
- Props are read-only. A child can read
count, but it can never reassign it. - Even if it could, changing a local copy wouldn’t tell the parent anything. The parent’s real state would stay the same.
- The only allowed way is the second one. The child calls a function the parent gave it, and the parent updates its own state.
Never reach into the parent
A child should never try to change the parent’s data on its own. It should only call the function passed down to it. The parent owns the data, so only the parent’s own function may change it.
✅ Best practices
A few small habits keep this pattern clean and easy to read.
- Name the function prop with
onand the parent function withhandle. SoonAddTaskpairs withhandleAddTask. Anyone reading your code instantly gets the roles. - Keep the actual state update in the parent’s function, not the child. The child just reports what happened.
- Pass the function by name, like
onClick={onIncrement}. Don’t call it while passing, that runs it too early. - Send the data the parent needs as an argument, like
onAddTask(text). Don’t make the parent guess.
One quick rule to remember
Data flows down through normal props. Events flow up by calling a function prop. That’s the full two-way conversation, built out of one-way pieces.
🧩 What You’ve Learned
- ✅ Data flows down through props, but a child sends things up by calling a function the parent passed down
- ✅ The parent defines a
handle...function that updates the parent’s own state - ✅ The parent passes it down as a prop named
on..., likeonAddItem={handleAddItem} - ✅ The child calls that function, often with a value, like
onAddItem(value), when something happens - ✅ The child must never change the parent’s data directly, because props are read-only
- ✅ The child decides when to send and what value to send; the parent decides what to do with it
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
In React, how does a child component send data up to its parent?
Why: Data only flows down through props. The parent passes a function down, and the child calls that function to send a value back up. The parent's function then updates the parent's state.
- 2
What is the usual naming convention for this pattern?
Why: The function prop is named with on (like onAddItem) to describe the event, and the parent's function is named with handle (like handleAddItem) to describe the work. So onAddItem pairs with handleAddItem.
- 3
Why can't a child just change the parent's data directly through props?
Why: Props are read-only. A child can read a prop but cannot reassign it, and even a local change would not reach the parent's state. The only allowed way is to call a function the parent passed down.
- 4
When passing a function prop to a child, what is the correct way to write it?
Why: Pass the function by name, like onIncrement={handleIncrement}. Adding parentheses would call it immediately during render instead of waiting for the event. A string or a number would not be the function the child needs to call.
🚀 What’s Next?
So now you can pass data down and send events back up. The next step uses both directions at once: moving shared state into a parent so two children can work together.