Parent and Child Components Communication in React
Source: www.pixabay.com
Introduction
In this tutorial, we’ll build a simple todo application to demonstrate how parent and child components communicate visa-vice in React. We’ll create four components namely App, ListTodo, AddTodo and Todo. The App is the parent component, and the children are ListTodo, AddTodo and Todo. Also, we’ll show how Todo is a child of TodoList component. To get data in child component, the parent component pass down a props object to it, and to get data from child component, the child component wil pass a method up containing the data to parent component. Let’s get started.
Pre-requisite:
- Basic knowledge of Javascript, and React
- Proficient in the use of any modern code editors i.e. VS Code
Step #1
-
Scaffold a React application. One quick and easy way to do this is to use the Create React App tool. See Create React App site for details - https://create-react-app.dev/
-
Create a React app project and give it the name - simple-topo-app
npx create-react-app simple-topo-app
Step #2
- Open the simple-search-app project in any code editor of your choice
- Open the App.js file and delete the content in the return statement.
- Then enter a heading text. Simple Todo App
<h2>Simple Todo App</h2>
Step #3
Create two function components in App.js above the App component, and named them AddTodo and ListTodos.
const AddTodo = () => <div> Add Todo </>; // The AddTodo component would be used to add todos to the app
const ListTodo = () => <div> List Todos </>; // The Listtodo component would be used to display the added todos to the app
Then, add both components in the render section of the App component below the heading text - Simple Todo App
const App = () => {
return (
<>
<h2> Simple Todo App </h2>
<ListTodos />
<AddTodo />
</>
)
};
Preview the app.
Step #4
Let's implement the ListTodo component to display initial to-do todos data on a load of the app. In the App component locate the ListTodos added in the previous step, and add a props - "The props are data passed into components and they are Read-only" to it, and assign an empty object {}
for now. You can name it todos as shown below or any other meaningful name that describes its function or use.
<ListTodo todos={} />
Then, add todos
state property and a setTodos
method using useState
Hook. The useState
is a Hook that lets you add state to function components to store local data. Then initialize it with initial load data as shown below:
const [tasks, setTasks] = useState([{ id: 1, title: "Go to Gym", description: "Go to gym at 7.00 am every weekday.." }, { id: 2, title: "Take the car to the auto shop", description: "Take the car to auto shop for routine maintenance }])
Next, pass the todos
property to the ListTodos
component as shown below.
<ListTodo todos={todos} />
Goto to the ListTodos component and add props to the function argument as shown below:
const ListTodos = (props) => <div> <h4>List Todos <h4><div/>;
Next, render or display the props tasks passed into the TaskList component as shown below:
const ListTodos = (props) => {
return (
<>
<h4>List Todos </h4>
<table>
<thead>
<tr>
<th> Name </th>
<th> Description </th>
</tr>
</thead>
<tbody>
{props.todos.map(todo => (<tr key={todo.id}>
<td>{todo.title} </td>
<td> {todo.description}</td>
</tr>
))}
</tbody>
</table>
</>
)
}
Preview the app.The app should display the initial todos data.
Step #5
Let's now implement the AddTodo component functionality for adding new todos to the app. In the App component, create a method, name it onAddTodoHandler
, then give it an argument of formvalues
as shown below. We’ll make use of the formValues
in a moment.
<ListTodo todos={} />const addTodoHandler = (formValues) => {
}
Next, pass the addTodoHandler
to the AddTodo component as shown below:
<AddTaskForm onAddTaskHandler={addTaskHandler} />
Goto to the AddTodo component and add props to the function argument as shown below:
const AddTodoForm = ({ onAddTodoHandler }) => {
}
Note: Here we are destructuring the onAddTaskHandler from the props object which can make our code cleaner instead of using props. Learn more about Destructuring
Next, In the AddTodo component, add the HTML form with Input and Textarea fields and a submit button below the heading text, and set the attributes as shown below.
const AddTodoForm = ({ onAddTodoHandler }) => {
return (
<form onSubmit={}> <div>
<input type="text" name="title" value={} id=name" placeholder="Enter namel" onChange={} />
<textarea name="description" value={} id=description" placeholder="Enter description" rows="3" onChange={} ></textarea>
<button type="submit">Add Todo</button> </form>
)
}
Let's explain the attributes, the value attribute would hold the form input and textarea data, and the value would be available in the onChange method.
Then, add the formValues
state property and a setFormValues
method using useState
Hook as shown below:
const [formValues, setFormValues] = useState({ title: '', description: '' })
Create an onChange method and give it an argument of event
. Then console.log out the value of event.target.value
.
const handleOnInputChange = (event) =>
{ setFormValues({ ...formValues, [event.target.name]: event.target.value }) }
Next, create a submit method and give it an argument of event
so we can access the preventDefault method for preventing the submit action from the browser default of refreshing the page on submit action. Then pass the formValues
to props onAddTodoHandler method passed in from the App component. Lastly here, clear the form values after submission.
const submitTodoHandler = (event) => {
event.preventDefault();
onAddTodoHandler(formValues)
setFormValues({ title: '', description: '' }) //clear the form values after submission.
}
Next, pass the submitTodoHandler method to the form submit attribute and the handleOnInputChange and state property - formValues
to the input and textarea onChange attributes respectively. See below:
<form onSubmit={submitTodoHandler}> <div>
<input type="text" name="title" value={formValues.name} id=name" placeholder="Enter namel" onChange={handleOnInputChange} />
<textarea name="description" value={formValues.description} id=description" placeholder="Enter description" rows="3" onChange={handleOnInputChange} ></textarea>
<button type="submit">Add Todo</button>
</form>
Step #6
Let's now implement the AddTodo component functionality for adding new todos to the app. In the App component, create a method, name it onAddTodoHandler
, then give it an argument of formvalues
as shown below. We’ll make use of the formValues
in a moment.
const addTaskHandler = (formValues) => {
const newTodo = { ...formValues, id: todos.length + 1 };
setTodos([...todos, newTodo])
}
Here we are getting the formValues
using the addTodoHandler passed into the AddTodo component. Then, generate a todo
id` using the ‘todos’ current length plus ‘1’ to create a new todo - newTodo. Then, concatenating the newTodo to the current todos in the state using the spread operator and setTodos - setTodos([...todos, newTodo])
Again, preview the app by entering a todo title and description.
Step #7
Decomposition is React way of composition. So let's decompose TodosList component by creating a new Todo component from TodosList to render a single todo item so that Todo is a child of TodoList, and TodoList is the immediate parent of the Todo. First, create a function component and name it Todo, and give it a props argument of {todo}
. Move the <td>{todo.title} </td>
and <td>{todo.description}</td>
code in the ListTodos to the Todo component render. Then, replace the moved code in ListTodo component with Todo component. See below for the changes to both components:
const Todo = ({ todo }) => (
// Note: The implicit `return` statement is used here for arrow function, so no need
// for `return` statement.
<> <td>{todo.title} </td>
<td> {todo.description}</td> </>
)
// Replace the moved code in ListTodo component with Todo componentN
<Todo todo={todo} />
)
Complete code for a Simple Todo App to demonstrate Parent-Child components communication in React. The Tailwind CSS has used styling.
import React, { useState } from "react";
import "./style.css";
import 'tw-elements';
const Todo = ({ todo }) => (
<>
<td className="py-4 px-6 font-weight-strong">{todo.title} </td>
<td className="py-4 px-6"> {todo.description}</td>
</>
)
const TodosList = ({ todos }) => (
<div className="block max-w-full shadow-md py-3 px-6">
<table className="w-full text-sm text-left ">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th className="py-3 px-6">
Name
</th>
<th className="py-3 px-6">
Description
</th>
</tr>
</thead>
<tbody>
{todos.map(todo => (
<tr
key={todo.id}
className="bg-white border-b dark:bg-gray-900 dark:border-gray-700"
>
<Todo todo={todo} />
</tr>
))}
</tbody>
</table>
</div>
)
const AddTodoForm = ({ onAddTodoHandler }) => {
const [formValues, setFormValues] = useState({ title: '', description: '' })
const handleOnInputChange = (event) => {
setFormValues({
...formValues,
[event.target.name]: event.target.value
})
}
const submitTodoHandler = (event) => {
event.preventDefault()
onAddTodoHandler(formValues)
setFormValues({ title: '', description: '' })
}
return (
<form onSubmit={submitTodoHandler}>
<div class="flex justify-left">
<input
type="text"
name="title"
value={formValues.title}
class="
form-control
m-0
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none
"
id="exampleFormControlInput1"
placeholder="Enter title"
onChange={handleOnInputChange}
/>
</div>
<div class="flex justify-left">
<textarea
name="description"
value={formValues.description}
class="
mb-3
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none
"
id="exampleFormControlTextarea1"
rows="3"
placeholder="Enter description"
onChange={handleOnInputChange}
></textarea>
</div>
<button type="submit" class=" inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out">Add Todo</button>
</form>
)
}
export default function App() {
const [todos, setTodos] = useState([{ id: 1, title: "Go to Gym", description: "Go to gym at 7.00 am every weekday..." }, { id: 2, title: "Take the car for repairs", description: "Take the car to auto shop for routine maintenance.." }])
const oddTodoHandler = (formValues) => {
event.preventDefault()
const newTodo = { ...formValues, id: todos.length + 1 }
setTodos([...todos, newTodo])
console.log("values", [...todos, newTodo])
}
return (
<>
<div class="flex justify-center container mx-auto">
<h3 class="font-medium leading-tight text-2xl mt-0 mb-2 text-blue-600">Simple Todo App</h3 >
</div>
<div className="max-w-12xl mx-auto grid grid-cols-12 bg-gray-200">
<div className="col-span-12">
<AddTodoForm onAddTodoHandler={oddTodoHandler} />
<TodosList todos={todos} />
</div>
</div>
</>
);
}
That's it. Happy coding!