Task

You want a grandchild component to be able to trigger its grandparent component’s method in React. You could pass the method as a prop down the family tree of components to the appropriate grandchild component (this is an example of prop drilling). Here is a demonstrative code sample:

Prop drilling pattern

But there are some issues with this prop drilling pattern:

Instead of passing a function down as a prop in order to let the grandchild update the grandparent’s state, you could also pass down an initial state (an object, not a function). Then, have the grandchild update that state locally however it likes. Here is an example of that:

Distributed state pattern

There are a number of issues with this distributed state pattern:

I’m here to offer you an alternative to these two approaches, which instead meets the following criteria:

Event Emitter Approach

The alternative I prefer is to use an event emitter. An event emitter establishes a direct line of communication between the two desired endpoints, removing the need to pass event information (data, callbacks) through intermediate components. The event emitter that I implemented has two types of actions — dispatch and subscribe. These aren’t special keywords; they could instead be called publish and subscribe (pub-sub), send and receive, speak and listen, egg and mango, etc. It is important to note that more actions can be defined and used, such as unsubscribe (which would be important in non-trivial applications).

Here is a real world metaphor to illustrate dispatch and subscribe. When the dog sees a person at the door, it dispatches a signal (“woof”), and by standing at the door, also sends the data that the woof was related to the door. The care-taker is tuned into woofs and knows to open the door when the dog woofs beside it. The dog itself couldn’t open the door and the person couldn’t see if someone was at the door so they worked together with their respective information and abilities to achieve this chain of events.

I didn’t go to art school

To demonstrate my event emitter in React, I’ll share pieces of an Instagram clone I’ve been working on as a student at Lambda School. First, I need to define the event emitter, which is simply an object that we can import to other components as needed.

// 2018 original version
const EventEmitter = {
_events: {},
dispatch: function (event, data) {
if (!this._events[event]) return;
this._events[event].forEach(callback => callback(data))
},
subscribe: function (event, callback) {
if (!this._events[event]) this._events[event] = [];
this._events[event].push(callback);
}}
module.exports = { EventEmitter };
// 2020 updated version - typescript & unsubscribeexport enum Events {
SET_TITLE = 'set_title'
// use an enum to keep track of events similar to action types set as variables in redux
}
export const eventEmitter = {
_events: {},
dispatch(event: Events, data: any) {
if (!this._events.[event]) return;
this._events[event].forEach(callback => callback(data))
},
subscribe(event: Events, callback: (data: any) => any) {
if (!this._events[event]) this._events[event] = [];
this._events[event].push(callback);
},
unsubscribe(event: Events) {
if (!this._events[event]) return;
delete this._events[event];
}
}
EventEmitter Object

Next, I want to import EventEmitter to components that will be subscribing to an event and then set up the subscribe functionality. The name of the event that you pass can be whatever you like. Because its purpose is to associate two signals, the name of the event just has to match in the corresponding dispatch and subscribe. Here, I decided to call the event by the name of the callback function.

Subscribe

Finally, I will repeat those same steps for the dispatch: import EventEmitter and set up the dispatch functionality. These are the dispatches that correspond to the two subscribes above.

Dispatch

Now your components have a direct line of communication to coordinate events!

Let’s revisit the benefits of this approach:

A few drawbacks to note:

Context

If this event emitter pattern seems useful to you, you’re not alone. Node, Vue, and Angular all feature some variation of built-in event emitters or an event bus. Redux handles this exact task, as well. It takes a bit more work to use an event emitter in pure React, but I’ve found the effort to be worthwhile. I welcome all corrections/questions/suggestions you’d like to share :)

See you next time!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store