React is designed around a predictable flow of data from parent components to child components. But real applications often need information to move in the opposite direction.
For example, a child component may contain a button, form, search field, or other interactive element. When the user interacts with it, the parent may need to know what happened.
React does not provide a special mechanism for directly sending data from a child component to its parent. Instead, the parent passes a function as a prop to the child. The child can then call that function and provide data as an argument.
This pattern is commonly called child-to-parent communication.
The basic flow looks like this:
Parent Component
│
│ passes function as prop
↓
Child Component
│
│ calls function
↓
Parent receives data
This pattern is one of the most important concepts to understand when building interactive React applications.
What Is Child-to-Parent Communication?
Child-to-parent communication occurs when a child component needs to send information or trigger an action in its parent.
React’s normal data flow goes downward:
Parent
↓
Child
But a child can notify the parent by calling a function that the parent provided:
Parent
↓
function prop
↓
Child
↓
calls function
↓
Parent
For example, suppose a parent component displays a counter and a child component contains the button that increments it.
The parent can define the state and the update function:
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
function increaseCount() {
setCount(count + 1);
}
return <CounterButton onIncrease={increaseCount} />;
}
The child receives the function:
function CounterButton({ onIncrease }) {
return (
<button onClick={onIncrease}>
Increase
</button>
);
}
When the user clicks the button, the child calls the function supplied by the parent.
The child does not directly change the parent’s state. It requests that the parent perform the update.
Why Is Child-to-Parent Communication Important?
Many interactive components need to report something back to the component that owns the data.
Consider a search page.
The parent may own the current search term:
const [searchTerm, setSearchTerm] = useState("");
A child search box can notify the parent whenever the user enters text.
Another example is a form.
The form component may be a child, while the parent needs the submitted information to:
- update state
- send data to an API
- display a confirmation message
- update another component
- navigate to another page
- perform validation
- update a shopping cart
Instead of allowing the child to directly access the parent’s state, React encourages the parent to provide a callback.
The Basic Pattern
The pattern has three main parts.
The Parent Creates a Function
The parent defines a function that handles the information coming from the child.
function App() {
function handleMessage(message) {
console.log(message);
}
return <Child onSendMessage={handleMessage} />;
}
The Parent Passes the Function to the Child
The function is passed as a prop:
<Child onSendMessage={handleMessage} />
Here, onSendMessage is simply a prop name chosen by the developer.
The Child Calls the Function
The child receives the function and calls it:
function Child({ onSendMessage }) {
return (
<button onClick={() => onSendMessage("Hello from the child!")}>
Send Message
</button>
);
}
When the button is clicked, the parent function receives the message.
A Simple Example
Let’s create a small example where a child component sends a message to its parent.
import { useState } from "react";
function App() {
const [message, setMessage] = useState("");
function handleMessage(newMessage) {
setMessage(newMessage);
}
return (
<div>
<h1>Parent Component</h1>
<p>Message: {message}</p>
<Child onSendMessage={handleMessage} />
</div>
);
}
function Child({ onSendMessage }) {
return (
<button onClick={() => onSendMessage("Hello from Child!")}>
Send Message
</button>
);
}
export default App;
When the user clicks the button, the parent displays:
Message: Hello from Child!
Understanding the Example Step by Step
The parent first creates state:
const [message, setMessage] = useState("");
The message value belongs to the parent.
Next, the parent creates a function:
function handleMessage(newMessage) {
setMessage(newMessage);
}
This function updates the parent’s state.
The parent passes the function to the child:
<Child onSendMessage={handleMessage} />
The child receives it:
function Child({ onSendMessage }) {
The child calls the function when the button is clicked:
onSendMessage("Hello from Child!")
The string "Hello from Child!" becomes the argument of the parent’s function.
The parent’s function receives it:
function handleMessage(newMessage) {
Then it updates the state:
setMessage(newMessage);
React re-renders the parent with the updated state.
The complete flow is:
Parent creates state
↓
Parent creates callback
↓
Callback passed to Child
↓
Child calls callback
↓
Data reaches Parent
↓
Parent updates state
↓
React re-renders UI
Sending Data from a Child
A child can send different types of data to its parent.
It could send:
- Strings
- Numbers
- Booleans
- Objects
- Arrays
- Form values
- IDs
- User selections
- Event-related information
For example:
function Child({ onSelect }) {
return (
<button onClick={() => onSelect(25)}>
Select
</button>
);
}
The parent receives the number:
function App() {
function handleSelect(value) {
console.log(value);
}
return <Child onSelect={handleSelect} />;
}
The console will contain:
25
Sending an Object to the Parent
You can also send an object.
This is particularly useful for forms.
function LoginForm({ onSubmit }) {
function handleSubmit() {
const user = {
email: "karim@example.com",
role: "Developer"
};
onSubmit(user);
}
return (
<button onClick={handleSubmit}>
Submit
</button>
);
}
The parent can receive the object:
function App() {
function handleLogin(user) {
console.log(user.email);
console.log(user.role);
}
return <LoginForm onSubmit={handleLogin} />;
}
The child sends the data, while the parent decides what to do with it.
Child-to-Parent Communication with Forms
Forms are one of the most common situations where this pattern is used.
Consider a child component containing an input field:
import { useState } from "react";
function SearchBox({ onSearch }) {
const [value, setValue] = useState("");
function handleSubmit(event) {
event.preventDefault();
onSearch(value);
}
return (
<form onSubmit={handleSubmit}>
<input
value={value}
onChange={(event) => setValue(event.target.value)}
placeholder="Search..."
/>
<button type="submit">
Search
</button>
</form>
);
}
The parent can receive the search term:
function App() {
function handleSearch(searchTerm) {
console.log("Searching for:", searchTerm);
}
return <SearchBox onSearch={handleSearch} />;
}
The child manages the input interaction, while the parent receives the submitted value.
Child-to-Parent Communication with State
A common pattern is to keep important state in the parent and let the child request changes.
For example:
import { useState } from "react";
function App() {
const [selectedColor, setSelectedColor] = useState("blue");
function handleColorChange(color) {
setSelectedColor(color);
}
return (
<div>
<h2>Selected Color: {selectedColor}</h2>
<ColorPicker onColorChange={handleColorChange} />
</div>
);
}
function ColorPicker({ onColorChange }) {
return (
<div>
<button onClick={() => onColorChange("red")}>
Red
</button>
<button onClick={() => onColorChange("green")}>
Green
</button>
<button onClick={() => onColorChange("blue")}>
Blue
</button>
</div>
);
}
The child does not directly manipulate selectedColor.
Instead:
Child
↓
onColorChange("red")
↓
Parent
↓
setSelectedColor("red")
↓
UI updates
This keeps state ownership clear.
Passing Event Information to the Parent
A child can pass information from an event to the parent.
For example:
function Input({ onChange }) {
return (
<input
onChange={(event) => onChange(event.target.value)}
/>
);
}
The child extracts the input value:
event.target.value
and sends it to the parent:
onChange(event.target.value)
The parent can then handle it:
function App() {
function handleChange(value) {
console.log("Input:", value);
}
return <Input onChange={handleChange} />;
}
This is often cleaner than exposing unnecessary implementation details to the parent.
Passing Arguments to Callback Functions
Sometimes the child needs to send additional information.
function Product({ id, onDelete }) {
return (
<button onClick={() => onDelete(id)}>
Delete
</button>
);
}
The parent can receive the ID:
function App() {
function handleDelete(productId) {
console.log("Delete product:", productId);
}
return (
<Product
id={101}
onDelete={handleDelete}
/>
);
}
When the button is clicked:
onDelete(id)
becomes:
handleDelete(101)
This pattern is extremely common in lists and CRUD applications.
Child-to-Parent Communication in Lists
Imagine a parent rendering several products.
function App() {
const products = [
{ id: 1, name: "Mouse" },
{ id: 2, name: "Keyboard" },
{ id: 3, name: "Headphones" }
];
function handleDelete(productId) {
console.log("Delete:", productId);
}
return (
<div>
{products.map((product) => (
<Product
key={product.id}
product={product}
onDelete={handleDelete}
/>
))}
</div>
);
}
The child receives the product and callback:
function Product({ product, onDelete }) {
return (
<article>
<h2>{product.name}</h2>
<button onClick={() => onDelete(product.id)}>
Delete
</button>
</article>
);
}
The child identifies which product was clicked and sends its ID to the parent.
The parent remains responsible for deciding how the product should actually be removed.
Updating an Array in the Parent
Suppose the parent owns a list of tasks.
import { useState } from "react";
function App() {
const [tasks, setTasks] = useState([
{ id: 1, title: "Learn React" },
{ id: 2, title: "Build a project" }
]);
function handleDelete(taskId) {
setTasks((currentTasks) =>
currentTasks.filter((task) => task.id !== taskId)
);
}
return (
<div>
{tasks.map((task) => (
<Task
key={task.id}
task={task}
onDelete={handleDelete}
/>
))}
</div>
);
}
The child can trigger the deletion:
function Task({ task, onDelete }) {
return (
<div>
<span>{task.title}</span>
<button onClick={() => onDelete(task.id)}>
Delete
</button>
</div>
);
}
Notice that the child does not mutate the tasks array.
The parent updates its state using filter() and creates a new array.
This follows React’s immutable update pattern.
Child-to-Parent Communication and Lifting State Up
Child-to-parent communication is closely related to a concept called lifting state up.
Suppose two components need access to the same piece of data.
Instead of allowing each component to maintain a separate copy, the state can be moved to their closest common parent.
The parent then:
- Owns the state.
- Passes the current value down as a prop.
- Passes a callback down as another prop.
- Lets the child request changes.
- Updates the state in the parent.
The structure looks like this:
Parent
/ \
State State
↓ ↓
Child A Child B
↑
callback
The child does not own the shared state. The parent does.
You will explore this pattern in more detail in the Lifting State Up chapter.
Child-to-Parent Communication vs Direct State Access
A child should not try to reach into its parent and directly access the parent’s state.
Avoid patterns where a child depends on the internal implementation of its parent.
Instead, define an explicit interface through props:
<Child
value={value}
onChange={handleChange}
/>
This makes the relationship easier to understand.
The child knows:
“I receive a value and I can request a change through
onChange.”
It does not need to know how the parent stores that value.
Naming Callback Props
There is no special React rule requiring callback props to use a specific name.
You could write:
<Child onSend={handleSend} />
or:
<Child onSelect={handleSelect} />
or:
<Child onDelete={handleDelete} />
Names beginning with on are common because they clearly communicate that the prop represents an action or callback.
Choose names that describe what the child is allowed to request.
For example:
onSubmit
onDelete
onSelect
onSearch
onChange
onSave
are clearer than:
function1
callback
doSomething
A Real-World Web4Learners Example
Imagine Web4Learners has a React course page.
The parent component displays the currently selected lesson:
import { useState } from "react";
function CoursePage() {
const [selectedLesson, setSelectedLesson] = useState(
"Introduction to React"
);
function handleLessonSelect(lesson) {
setSelectedLesson(lesson);
}
return (
<div>
<h1>{selectedLesson}</h1>
<LessonList onLessonSelect={handleLessonSelect} />
</div>
);
}
The child contains the lesson buttons:
function LessonList({ onLessonSelect }) {
const lessons = [
"Introduction to React",
"Props",
"State",
"Parent to Child",
"Child to Parent"
];
return (
<div>
{lessons.map((lesson) => (
<button
key={lesson}
onClick={() => onLessonSelect(lesson)}
>
{lesson}
</button>
))}
</div>
);
}
When a user clicks State, the child calls:
onLessonSelect("State")
The parent receives the value:
handleLessonSelect("State")
and updates:
setSelectedLesson("State");
The heading then changes to:
State
This is a realistic example of child-to-parent communication because the child handles the user’s interaction while the parent owns the application state.
Common Mistakes
Trying to Directly Change Parent State
A child cannot directly access a parent’s local state.
Incorrect:
function Child() {
parentCount = parentCount + 1;
}
The child should instead receive a callback:
<Child onIncrease={handleIncrease} />
and call it:
onIncrease();
Calling the Callback During Rendering
Be careful not to accidentally call a callback while rendering.
Incorrect:
<button onClick={onDelete(id)}>
Delete
</button>
This calls onDelete immediately during rendering.
Use a function instead:
<button onClick={() => onDelete(id)}>
Delete
</button>
Now the callback runs when the button is clicked.
Mutating Parent Data
If a parent passes an object or array to a child, the child should not directly mutate it.
Avoid:
function Child({ user }) {
user.name = "New Name";
return <div>{user.name}</div>;
}
Instead, the parent should own the update and expose an appropriate callback.
Passing Too Many Callbacks
Callbacks are useful, but a component that receives a very large number of callbacks may indicate that the component responsibilities or state ownership need to be reconsidered.
Keep component interfaces focused.
Confusing Props with State
The child receives the callback through props:
function Child({ onSave }) {
The child does not need to store the callback in state.
It simply calls it when appropriate:
onSave(data);
Best Practices
Keep State Where It Belongs
If the parent needs to coordinate data between multiple children, keeping the state in the parent is often appropriate.
Pass Only the Information the Parent Needs
If the parent only needs an ID, send the ID:
onDelete(product.id)
There is no need to send an entire object if the parent does not need it.
Use Clear Callback Names
Prefer:
onSubmit
onDelete
onSelect
onSearch
because the purpose is immediately understandable.
Let the Parent Own Important State
If the parent needs to make decisions based on a value, it often makes sense for the parent to own that state and provide callbacks to children.
Avoid Direct Mutation
When updating arrays or objects in state, use immutable update patterns.
For arrays:
setItems((currentItems) =>
currentItems.filter((item) => item.id !== id)
);
For objects:
setUser((currentUser) => ({
...currentUser,
name: "Karim"
}));
Use Functional State Updates When Based on Previous State
If the new state depends on the previous state, prefer the updater form:
setCount((currentCount) => currentCount + 1);
This makes the dependency on the previous state explicit.
Image Placement Instruction
Image placement: Add a visual diagram immediately after the section “The Basic Pattern”.
The image should show the complete child-to-parent communication flow:
┌──────────────────────────┐
│ Parent Component │
│ │
│ handleMessage(data) │
└────────────┬─────────────┘
│
│ passes callback
↓
┌──────────────────────────┐
│ Child Component │
│ │
│ onSendMessage(data) │
└────────────┬─────────────┘
│
│ calls callback
↓
┌──────────────────────────┐
│ Parent receives │
│ data │
│ │
│ Updates state │
└──────────────────────────┘
Use a modern Web4Learners design with a clean React development aesthetic, rounded cards, subtle gradients, modern typography, and clear directional arrows. The main visual focus should be the callback moving from the parent to the child and the resulting data/action being sent back to the parent.
Second image placement: Add a visual example after “Child-to-Parent Communication with Forms”.
Show a simple form component sending submitted data to a parent:
User types data
↓
Child Form
↓
onSubmit(data)
↓
Parent Component
↓
Updates State
Keep the graphic visually simple and beginner-friendly.
Practice Exercise
Create a React application containing a parent component called App and a child component called MessageForm.
The child should contain an input field and a button.
The user should be able to enter a message such as:
Hello Web4Learners
When the button is clicked, the child should send the message to the parent.
The parent should then display:
Message from child: Hello Web4Learners
Challenge
Extend the application so the child sends an object instead of a simple string.
For example:
{
name: "Karim",
message: "I am learning React"
}
The parent should display both values.
Then add another child component called MessageList and pass the messages from the parent to that component.
This will give you practice with:
- Props
- Callback functions
- State
- Child-to-parent communication
- Parent-to-child communication
- Rendering arrays
Cheat Sheet
| Task | Example |
|---|---|
| Create callback | function handleSave(data) {} |
| Pass callback | <Child onSave={handleSave} /> |
| Receive callback | function Child({ onSave }) {} |
| Call callback | onSave(data) |
| Pass an ID | onDelete(product.id) |
| Pass a string | onSelect("React") |
| Pass an object | onSubmit(formData) |
| Handle input | onChange(event.target.value) |
| Update parent state | setValue(data) |
| Functional state update | setCount(c => c + 1) |
| Child triggers parent action | onSave(data) |
Key Takeaways
- React’s normal data flow is from parent to child.
- A child can communicate with its parent by calling a function passed through props.
- The parent defines the callback and passes it to the child.
- The child calls the callback when an event or interaction occurs.
- The child can pass data to the callback as an argument.
- The data can be a string, number, boolean, object, array, form value, ID, or another JavaScript value.
- The child does not directly modify the parent’s state.
- The parent remains responsible for updating its own state.
- Callback props are especially useful for buttons, forms, selections, search components, lists, and CRUD operations.
- Use clear callback names such as
onSubmit,onDelete,onSelect, andonChange. - Avoid directly mutating objects or arrays owned by the parent.
- Child-to-parent communication works closely with lifting state up.
- Understanding this pattern is essential for building React applications with multiple interactive components.