React Child to Parent Communication

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. Like onAddItem, onIncrement, onDelete. It describes the event.
  • The actual function in the parent starts with handle. Like handleAddItem, 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 it
function 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 calls setCount to update the parent’s own state.
  • The parent passes it down as onIncrement={handleIncrement}.
  • The child’s button runs onIncrement when it’s clicked. The child doesn’t know or care what that function does.
  • That call runs handleIncrement back 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 submitted
function 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 task
function 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, handleAddTask receives it as newTask and 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 it
function 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 on and the parent function with handle. So onAddTask pairs with handleAddTask. 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..., like onAddItem={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. 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. 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. 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. 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.

React Lifting State Up

Share & Connect