Log In

React component communication


by Yariv Katz

In this lesson we will cover common patterns for communication between components. We will create a new react application and we will create App component with 2 children components. We will then explore how to communicate between those components.

Creating our playground

Let's start by creating a new react application. We will use create-react-app to start our project.

> npx create-react-app react-component-comm
> cd react-component-comm

When we create a new react application with the cli, there is already an App component wrapping our react application. This will be our parent component. We will also want to create 2 child components for our react communication playground. In the src folder, create a folder called components. In the components folder create a new file called Child1.js with the following code:

import React from 'react';

export default function Child1(props) {
    return (
        <h1>I'm child 1</h1>
    )
}

This simple component will simply display a message showing us that child 1 is displayed. We will do a similar thing with another child component. Create another file in the components folder called Child2.js with the following:

import React from 'react';

export default function Child2(props) {
    return (
        <h1>I'm child 2</h1>
    )
}

We will update the App component to place our 2 components. In the src folder update the App.js to this:

import React, { Component } from 'react';
import Child1 from './components/Child1';
import Child2 from './components/Child2';

class App extends Component {
    render() {
        return (
            <>
                <Child1 />
                <Child2 />
            </>
        );
    }
}

export default App;

Our playground is now ready, let's start exploring how we communicate between the different components.

Props

Props are a way we can communicate between a parent and a child by passing data to a child. to demonstrate props let's transfer data from the App to Child1 and display it on Child1. Modify the App.js to the following:

import React, { Component } from 'react';
import Child1 from './components/Child1';
import Child2 from './components/Child2';

class App extends Component {

    constructor(props) {
        super(props);
        this.message = 'hello';
    }
    
    render() {
        return (
            <>
                <Child1 message={this.message} />
                <Child2 />
            </>
        );
    }
}

export default App;

Now let's display that property in the child:

import React from 'react';

export default function Child1(props) {
    return (
        <h1>I'm child 1 {props.message}</h1>
    )
}

We can also use the props to pass a callback function, to communicate from child to parent. Let's demonstrate this usage by creating a button in Child1, when the button is pressed Child1 will call a callback passed from the parent with the number of times that button was pressed. The parent will print the counter.

Modify the Child1 like so:

import React from 'react';

let counter = 0;
export default function Child1(props) {
    
    const increaseCounter = () => {
        counter++;
        props.cb(counter);
    }

    return (
        <>
            <h1>I'm child 1 {props.message}</h1>
            <button onClick={increaseCounter}>press me</button>
        </>
    )
}

Pressing the button will call the increaseCounter function which will raise the counter variable and and call the callback function passing the counter to the parent.

Now on the parent component. Modify the App.js as follows.

import React, { Component } from 'react';
import Child1 from './components/Child1';
import Child2 from './components/Child2';

class App extends Component {
    state = {
        counter: 0
    }

    constructor(props) {
        super(props);
        this.message = 'hello';
    }

    childCounter = (counter) => {
        this.setState({
        counter
        })
    }
    
    render() {
        return (
            <>
                <h1>Counter {this.state.counter}</h1>
                <Child1 message={this.message} cb={this.childCounter} />
                <Child2 />
            </>
        );
    }
}

export default App;

through the callback passed to the child properties we are transfering data from the child back to the parent. We are then setting that data in the parent state which will call the render with the updated counter and display in inside h1.

We can also now transfer the counter from the parent to child 2 and by doing so send information from child1 to child 2. Modify App.js

import React, { Component } from 'react';
import Child1 from './components/Child1';
import Child2 from './components/Child2';

class App extends Component {
    state = {
        counter: 0
    }

    constructor(props) {
        super(props);
        this.message = 'hello';
    }

    childCounter = (counter) => {
        this.setState({
            counter
        })
    }
    
    render() {
        return (
            <>
                <h1>Counter {this.state.counter}</h1>
                <Child1 message={this.message} cb={this.childCounter} />
                <Child2 counter={this.state.counter} />
            </>
        );
    }
}

export default App;

And now in child2 we can print the counter from the props. Modify Child2.js

import React from 'react';

export default function Child2(props) {
    return (
        <h1>I'm child 2 {props.counter}</h1>
    )
}

So another way we can communicate between 2 children is by passing data through the state of the parent.

Children

We can pass data from parent to children through the prop children. This can be done by placing valid jsx between the tags of the children. For example let's say the parent wants to pass an h1 tag to the child. Modify the App.js as follows:

import React, { Component } from 'react';
import Child1 from './components/Child1';
import Child2 from './components/Child2';

class App extends Component {
    state = {
        counter: 0
    }

    constructor(props) {
        super(props);
        this.message = 'hello';
    }

    childCounter = (counter) => {
        this.setState({
            counter
        })
    }
    
    render() {
        return (
            <>
                <h1>Counter {this.state.counter}</h1>
                <Child1 message={this.message} cb={this.childCounter} >
                <h1>Hello from parent - {this.message}</h1>
                </Child1>
                <Child2 counter={this.state.counter} />
            </>
        );
    }
}

export default App;

Notice that we can pass entire react elements, other components, arrays, number, string or null as children. The context of the items passed is in the parent so in this example we are printing in the h1 tag a property in the parent.

In the child in order to use the elements passed we can grab them from the props.children modify Child1.js to place the children:

import React from 'react';

let counter = 0;
export default function Child1(props) {
    
    const increaseCounter = () => {
        counter++;
        props.cb(counter);
    }

    return (
        <>
            <h1>I'm child 1 {props.message}</h1>
            <button onClick={increaseCounter}>press me</button>
            {props.children}
        </>
    )
}

ref

Another way to communicate between a parent component and a child component is with ref. By using ref the parent can grab the instance of the child component and directly access properties and methods on the child class instance. To understand how it's done let's create a method in the child that return a string and let's call the instance method from the parent. Ref can be attached to dom tags and then we will get an instance of the dom object react placed. We cannot place ref to a function component so we will have to convert Child1 to a class component Modify Child1.js to this:

import React, {Component} from 'react';

export default class Child1 extends Component {
    counter = 0;

    increaseCounter = () => {
        this.counter++;
        this.props.cb(this.counter);
    }

    sayHello = () => {
        return 'hello from child1';
    }

    render() {
        return (
            <>
                <h1>I'm child 1 {this.props.message}</h1>
                <button onClick={this.increaseCounter}>press me</button>
                {this.props.children}
            </>
        )
    }
}

We converted the function component to class component and implemented the render method which returns what the function component returned before. We also added a method sayHello that we will try to call it from the parent. Let's see how we can use that function from the parent. Modify the file App.js to this:

import React, { Component } from 'react';
import Child1 from './components/Child1';
import Child2 from './components/Child2';

class App extends Component {
    state = {
        counter: 0,
        msgFromChild: ''
    }

    constructor(props) {
        super(props);
        this.message = 'hello';
        this.child1 = React.createRef();
    }

    childCounter = (counter) => {
        this.setState({
            counter
        })
    }
    
    componentDidMount() {
        this.setState({
            msgFromChild: this.child1.current.sayHello()
        })
    }

    render() {
        return (
            <>
                <h1>Counter {this.state.counter}</h1>
                <h1>{this.state.msgFromChild}</h1>
                <Child1 ref={this.child1} message={this.message} cb={this.childCounter} >
                <h1>Hello from parent - {this.message}</h1>
                </Child1>
                <Child2 counter={this.state.counter} />
            </>
        );
    }
}

export default App;

Notice how we created the ref in the constructor and then added the property ref to our child component. We added the message from the child in the state and in the componentDidMount we are setting the state with the sayHello call. So we see here that ref can also be used to communicate between components.

Services

What happens if our tree of component is more complex than this example and we want to communicate between two components which are very far from each other in our tree of components. Looking for the common father and starting to pass the information from child1 up to the father and then down again the child2 can be too complex. a much easier way to communicate in that case will be to use services.

We will use a service to transfer data between child1 and child2. We will add a button on child1 and when pressed it will transfer a random number to child2.

To communicate between the two we will use rxjs. With rxjs we can create a data stream which will pulse data whne child1 wants to transfer data, and we can also attach a listener to that data stream so child2 can subscribe a callback that will be called when the data is sent.

Let's start with installing the rxjs library. In the terminal type:

> npm install rxjs --save

In the src folder, create another folder called services and in there create a file called message.js with the following:

import { Subject } from 'rxjs';

class Message {
    constructor() {
        this.message = new Subject();
    }
}

export default new Message();

We are exporting an instance of a class with a subject exposed we can use to transfer information with and subscribe to to listen to the information passed. In the Child1 we need to create a button that when clicked will send information using that subject. Modify Child1.js

import React, {Component} from 'react';
import message from '../services/message';

export default class Child1 extends Component {
    counter = 0;

    increaseCounter = () => {
        this.counter++;
        this.props.cb(this.counter);
    }

    sayHello = () => {
        return 'hello from child1';
    }

    transferToChild2 = () => {
        message.message.next(Math.random());
    }

    render() {
        return (
            <>
                <h1>I'm child 1 {this.props.message}</h1>
                <button onClick={this.increaseCounter}>press me</button>
                {this.props.children}
                <button onClick={this.transferToChild2}>
                    Transfer to child2
                </button>
            </>
        )
    }
}

We added a button which calls a method that uses the service instance we created and calls next on the subject in that instace and passing random number to the subject.

Now in Child2 let's add a listeners to that subject and modify the state when new data arrives. To use state in Child2 we will have to transform the component from function to class. Modify Child2 as follows.

import React, {Component} from 'react';
import message from '../services/message';

export default class Child2 extends Component {
    state = {
        numberFromChild: 0
    }

    componentDidMount() {
        message.message.subscribe((num) => {
            this.setState({
                numberFromChild: num
            })
        });
    }
    
    render() {
        return (
            <>
                <h1>I'm child 2 {this.props.counter}</h1>
                <h5>
                    This number is from child1: {this.state.numberFromChild}
                </h5>

            </>
        )
    }
}

Notice the in the componentDidMount we are taking the message from the service and subscribing to listen to data transfered on the subject. When the data arrives we grab it from the subject and modify the state.

Redux / Mobx

We won't cover those methods in this lesson, there will be entire lessons about those in our blog, but we can also use state management libraries to place data in the state and share that data among different components.

Summary

In this lesson we covered common practice for communicating between react components. In my opinion the most important, useful and easy technique we can use is by using services and rxjs to facilate us in component communications. Hope you enjoyed this lesson and happy coding...