How to Handle Communication Between Parent and Child Components in ReactJS
Feb 6, 2020 • 11 Minute Read
Introduction
As easy as passing data between React components seems, it can get a bit complex at times. Although passing data from parent to child and vice-versa is an upfront process, passing data to and from siblings requires specific tools that React and JavaScript provide.
Our focus in this guide will be around the most straightforward methods of passing data among components. Also, to make things really interesting, we'll go through an example that involves building a zombie battle app.
Setup
The first step is creating a new React app, and we can use this repository. As its name suggests, the create-react-app repository will allow us to create a new React app quickly. For guidelines, refer to the instructions within.
Now that we have created our React app (let's name it zombie-battle), we can create a components folder inside the src folder. Within the components folder, we create two folders: Zombie and GameMgr, each of which must include an index.jsx file inside of which we will code our components.
Next, let's give our app a go. We'll type localhost:3000 in the address bar of our browser and check that the app gets launched. Note that 3000 is the default port number. However, it can vary.
Now that we have ensured everything is working as it should be, we can replace the existing App.js with the following:
import React, { Component } from 'react';
import './App.css';
import GameMgr from './components/GameMgr';
class App extends Component {
render() {
return (
<div className="App">
<GameMgr />
</div>
);
}
}
export default App;
Since we do not have the GameMgr component yet, our app will fail to start. However, if we place the following code snippets within the corresponding index.jsx file, it will work like a charm.
Parent to Child
Each zombie battle includes two opposing zombies chosen by their respective trainers. The zombie with the normal status and the faster pace is the first to attack (of course there are other factors to consider, but we're sticking to the basics for the sake of simplicity). Whenever the hit points (HP) of a zombie drop to 0, the zombie faints and the opposing zombie is declared the winner. On the other hand, in order for a zombie to attack, its trainer must pick one of the moves that the zombie learned earlier.
Back to React and its components. Passing values from a parent component to a child component is simple; we only have to pass the values as props of the child element. To illustrate this concept, take a look at the following code. This code is equivalent to the zombie selection process performed by the trainers. In this battle, we are sending a Humbug and a Geek to fight.
import React, { Component } from 'react';
import Zombie from '../Zombie';
const Humbug = {
name: 'Humbug',
level: 5,
hp: 20
}
class GameMgr extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<Zombie name={"Geek"} level={5} hp={21}></Zombie>
<Zombie {...Humbug}></Zombie>
</div>
)
}
}
export default GameMgr;
We can clearly see that the information regarding Geek is passed to the first instance of the Zombie component that is in the render function of the GameMgr component. The props are initiated separately and in succession.
The information regarding Humbug, however, is passed by means of a JavaScript object (defined as a constant after import) through the spread operator (ES6). This way, every key-value pair of this object is addressed as a prop of the child component.
In the next step, having passed the necessary information regarding our zombies, we will extract the information within the Zombie component. In order to do this, we need to access the props attribute of the component, as shown in the code below:
import React, { Component } from 'react';
class Zombie extends Component {
render() {
return(
<div>
<h1>Zmb: {this.props.name}</h1>
<ul style={{listStyle: 'none'}}>
<li><span>Level: {this.props.level}</span></li>
<li><span>hp: {this.props.hp}/{this.props.hp}</span></li>
</ul>
</div>
)
}
}
export default Zombie;
Having accessed the props attribute of the component, we can now render the information regarding each zombie separately within the render method. It is worth mentioning that the props of a component are not confined to the render as we can access them from within any further method that we create.
Child-to-Parent
In order for a Zombie to launch an attack, the trainer of said zombie must decide the desired move out of the list of moves the Zombie has learned. Once the trainer selects the move that zombie ought to use, this information needs to be passed on to the GameMgr component. Yet how can the child component (Zombie in our case) pass that information to the parent component (GameMgr)?
The simple, straightforward (and rather popular) approach for doing this is by passing a function that will behave as a callback. The method needs to receive the information that the child needs to pass to the parent as arguments.
Notice the getAttack method in the code below. The method signature indicates that it includes two parameters. The same method is passed later as a prop of the child component sans arguments.
import React, { Component } from 'react';
import Zombie from '../Zombie';
const Humbug = {
name: 'Humbug',
level: 5,
hp: 20,
type: 'cpu',
moves: {
move_1: {
name: "Tackle",
power: 4
},
move_2: {
name: "Growl",
power: 0
}
}
}
class GameMgr extends Component {
constructor(props) {
super(props);
this.state = {}
}
getAttack = (zombie, move) => {
let newState = this.state;
newState = {
attack: {
zombie: zombie,
move: move.name
}
}
this.setState(newState);
}
showMessage = () => {
if(this.state.attack) {
return(
<div>
<p>{`${this.state.attack.zombie} uses ${this.state.attack.move}!`}</p>
</div>
)
}
return false;
}
render() {
return(
<div>
<Zombie
type='player'
name={"Geek"}
level={6}
hp={11}
moves={{move_1: {name: "Scratch", power: 5},
move_2: {name: "Leer", power: 0}}}
sendAttack={this.getAttack.bind(this)}>
</Zombie>
<Zombie {...Humbug} sendAttack={this.getAttack.bind(this)}></Zombie>
{ this.showMessage() }
</div>
)
}
}
export default GameMgr;
If it seems a bit strange to you, take a look at the Zombie component after adjustment.
import React, { Component } from 'react';
class Zombie extends Component {
attack = (move) => {
this.props.sendAttack(this.props.name, move);
}
render() {
return(
<div>
<h1>Zmb: {this.props.name}</h1>
<ul style={{listStyle: 'none'}}>
<li><span>Level: {this.props.level}</span></li>
<li><span>hp: {this.props.hp}/{this.props.hp}</span></li>
</ul>
<ul style={{listStyle: 'none'}}>
<li><button onClick={() => this.attack(this.props.moves.move_1)}>{this.props.moves.move_1.name}</button></li>
<li><button onClick={() => this.attack(this.props.moves.move_2)}>{this.props.moves.move_2.name}</button></li>
<li><span>-</span></li>
<li><span>-</span></li>
</ul>
</div>
)
}
}
export default Zombie;
Here we have a new attack method that, upon execution, calls the very same method that was earlier passed a prop of the Zombie component. However, now the method includes two arguments: the zombie name (which is, in turn, a prop of the component) and the chosen attack object.
Further, in order to make the process more engaging, the callback method of the onClick event is set to the attack method in the buttons below. When a user clicks on the button to select an attack, the attached method (attack in our case) is called.
The attack method's only job is to call the getAttack of the GameMgr component. Now the zombie name along with the attack selected by its trainer is passed on to the parent component by means of that function.
This information is then stored in the GameMgr state. The render method will trigger again once the state is changed, thus displaying the message that the method showMessage produced. Since the passed information was stored in the state, it follows that all methods that use it will have access to said information (zombie name and the selected attack).
To keep it simple, the showMessage method will only display a message that contains the name of the zombie and the name of its attack.
Hence, we can perform more complex operations by passing methods to the child component and storing the passed data from the child to the parent's state, for instance, passing in information about the attack to the zombie that will receive the hit along with the damage to its hit points—or, if that zombie is lucky, the fact that the defender will avoid the hit.
And this is how the child component (Zombie) passed data to its parent (GameMgr).
Conclusion
In this guide, we had a look at two simple examples to illustrate how information is passed between two components. These examples demonstrate the simplest logic for passing data. However, there exist other approaches to perform this, depending on the use case.
We can use our imaginations to mix the two ways to enable communication between siblings. Nevertheless, if the components don't share any relation, i.e., a parent component, we can solve this with Redux. Also, in cases that use Context, it uses a central store such that both the parent and the child component can both read as well as update. Redux or React-Redux have similar behavior, i.e., the components can read or write from central store.