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.
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
.
1const eventBus = {
2 on(event, callback) {
3 // ...
4 },
5 dispatch(event, data) {
6 // ...
7 },
8 remove(event, callback) {
9 // ...
10 },
11};
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// ...
2on(event, callback) {
3 document.addEventListener(event, (e) => callback(e.detail));
4},
5// ...
The dispatch()
method will fire an event using the CustomEvent
API along with some data.
1// ...
2dispatch(event, data) {
3 document.dispatchEvent(new CustomEvent(event, { detail: data }));
4},
5// ...
Lastly, the remove()
method will remove the attached event from the document
object to prevent memory leakage in the app.
1// ...
2remove(event, callback) {
3 document.removeEventListener(event, callback);
4},
5// ...
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// Coupon Component
2
3import eventBus from "./EventBus";
4
5class Coupon extends Component {
6 constructor(props) {
7 super(props);
8 this.state = {
9 couponCode: "",
10 };
11 }
12
13 applyCoupon = () => {
14 console.log("applying");
15 eventBus.dispatch("couponApply", { message: "coupone applied" });
16 };
17
18 render() {
19 return (
20 <div>
21 <input
22 value={this.state.couponCode}
23 onChange={(e) => this.setState({ couponCode: e.target.value })}
24 />
25 <button onClick={this.applyCoupon}>Apply Coupon</button>
26 </div>
27 );
28 }
29}
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// Message Component
2
3import eventBus from "./EventBus";
4
5class Message extends Component {
6 constructor(props) {
7 super(props);
8 this.state = {
9 message: "",
10 };
11 }
12
13 componentDidMount() {
14 eventBus.on("couponApply", (data) =>
15 this.setState({ message: data.message })
16 );
17 }
18
19 componentWillUnmount() {
20 eventBus.remove("couponApply");
21 }
22
23 render() {
24 return <div>{this.state.message}</div>;
25 }
26}
You can check out the entire code for your reference in this section.
1const eventBus = {
2 on(event, callback) {
3 document.addEventListener(event, (e) => callback(e.detail));
4 },
5 dispatch(event, data) {
6 document.dispatchEvent(new CustomEvent(event, { detail: data }));
7 },
8 remove(event, callback) {
9 document.removeEventListener(event, callback);
10 },
11};
12
13export default eventBus;
1import React, { Component } from "react";
2
3import eventBus from "./EventBus";
4
5class Coupon extends Component {
6 constructor(props) {
7 super(props);
8 this.state = {
9 couponCode: "",
10 };
11 }
12
13 applyCoupon = () => {
14 console.log("applying");
15 eventBus.dispatch("couponApply", { message: "coupone applied" });
16 };
17
18 render() {
19 return (
20 <div>
21 <input
22 value={this.state.couponCode}
23 onChange={(e) => this.setState({ couponCode: e.target.value })}
24 />
25 <button onClick={this.applyCoupon}>Apply Coupon</button>
26 </div>
27 );
28 }
29}
30
31export default Coupon;
1import React, { Component } from "react";
2import eventBus from "./EventBus";
3
4class Message extends Component {
5 constructor(props) {
6 super(props);
7 this.state = {
8 message: "",
9 };
10 }
11
12 componentDidMount() {
13 eventBus.on("couponApply", (data) =>
14 this.setState({ message: data.message })
15 );
16 }
17
18 componentWillUnmount() {
19 eventBus.remove("couponApply");
20 }
21
22 render() {
23 return <div>{this.state.message}</div>;
24 }
25}
26
27export default Message;
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.