Author avatar

Gaurav Singhal

How to Communicate between Independent Components in React

Gaurav Singhal

  • Jul 3, 2020
  • 6 Min read
  • 1,555 Views
  • Jul 3, 2020
  • 6 Min read
  • 1,555 Views
Web Development
Front End Web Development
Client-side Framework
React

Introduction

One of the best features of React is that it helps you build a componentized app. You can build smaller components, each with its own responsibility, and then combine them to create a complete app. Often these components need to communicate with each other to pass data around the app.

React uses props to communicate between dependent components. Still, there are instances when you might want to communicate between two independent components that don't have a common functionality to share data. In this guide, you will learn how to communicate between independent components using an event bus.

Creating an Event Bus

To communicate between two independent components in React, you have the flexibility to set up a global event-driven system, or a PubSub system. An event bus implements the PubSub pattern and allows you to listen and dispatch events from components. The event bus helps you to decouple your components and pass data using events fired from other components so that they don't have direct dependencies between each other.

An event bus has three methods: on, dispatch, and remove.

1
2
3
4
5
6
7
8
9
10
11
const eventBus = {
  on(event, callback) {
    // ...
  },
  dispatch(event, data) {
    // ...
  },
  remove(event, callback) {
    // ...
  },
};
js

In the on() method, attach an event listener to the document object. The on method will have two arguments: the event and the callback function. The callback function will be called when the event gets fired.

1
2
3
4
5
// ...
on(event, callback) {
    document.addEventListener(event, (e) => callback(e.detail));
},
// ...
js

The dispatch() method will fire an event using the CustomEvent API along with some data.

1
2
3
4
5
// ...
dispatch(event, data) {
    document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
// ...
js

Lastly, the remove() method will remove the attached event from the document object to prevent memory leakage in the app.

1
2
3
4
5
// ...
remove(event, callback) {
    document.removeEventListener(event, callback);
},
// ...
js

Using the Event Bus for Communication

Now that you have created a custom event bus, it's time to use it to pass data between the components.

Let's say that that you have two components: a Coupon component and a Message component. The Coupon component has a text field and a submit button. When the submit button is clicked, the component triggers a couponApply event along with a custom message. The Message component listens for the couponApply event and displays the message that is passed in the event.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Coupon Component

import eventBus from "./EventBus";

class Coupon extends Component {
  constructor(props) {
    super(props);
    this.state = {
      couponCode: "",
    };
  }

  applyCoupon = () => {
    console.log("applying");
    eventBus.dispatch("couponApply", { message: "coupone applied" });
  };

  render() {
    return (
      <div>
        <input
          value={this.state.couponCode}
          onChange={(e) => this.setState({ couponCode: e.target.value })}
        />
        <button onClick={this.applyCoupon}>Apply Coupon</button>
      </div>
    );
  }
}
jsx

In the Message component, listen to the event inside the componentDidMount() lifecycle method and use the remove() method inside the componentWillUnmount() method of the component. When the component unmounts from the DOM, React will clean up the event listener and avoid memory leakage, which can cause performance issues in the app.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Message Component

import eventBus from "./EventBus";

class Message extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: "",
    };
  }

  componentDidMount() {
    eventBus.on("couponApply", (data) =>
      this.setState({ message: data.message })
    );
  }

  componentWillUnmount() {
    eventBus.remove("couponApply");
  }

  render() {
    return <div>{this.state.message}</div>;
  }
}
jsx

Complete Code

You can check out the entire code for your reference in this section.

EventBus.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const eventBus = {
  on(event, callback) {
    document.addEventListener(event, (e) => callback(e.detail));
  },
  dispatch(event, data) {
    document.dispatchEvent(new CustomEvent(event, { detail: data }));
  },
  remove(event, callback) {
    document.removeEventListener(event, callback);
  },
};

export default eventBus;
js

Coupon.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component } from "react";

import eventBus from "./EventBus";

class Coupon extends Component {
  constructor(props) {
    super(props);
    this.state = {
      couponCode: "",
    };
  }

  applyCoupon = () => {
    console.log("applying");
    eventBus.dispatch("couponApply", { message: "coupone applied" });
  };

  render() {
    return (
      <div>
        <input
          value={this.state.couponCode}
          onChange={(e) => this.setState({ couponCode: e.target.value })}
        />
        <button onClick={this.applyCoupon}>Apply Coupon</button>
      </div>
    );
  }
}

export default Coupon;
jsx

Message.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { Component } from "react";
import eventBus from "./EventBus";

class Message extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: "",
    };
  }

  componentDidMount() {
    eventBus.on("couponApply", (data) =>
      this.setState({ message: data.message })
    );
  }

  componentWillUnmount() {
    eventBus.remove("couponApply");
  }

  render() {
    return <div>{this.state.message}</div>;
  }
}

export default Message;
jsx

Conclusion

Using an event bus to communicate between React components is not conventional, but it is useful when it comes to communication between decoupled or independent components. When working on extensive apps, you won't want to use a custom event bus, but instead rely on other libraries like PubSub.js or Redux. Context API can also be used to pass data between components, but that requires additional wrapper components, which might make the component challenging to maintain.

9