Following my last article regarding event handling in both React and React Native, I have had questions regarding how to emit custom events in React Native. Programming in React Native means you are no longer in the DOM, and tools like document.createEvent('myCustomEvent') are not available.

So, how do you manage to achieve a similar result?

Let’s figure that one out :-)

Why are events important in React native?

At its core, React Native is simply a Javascript-Java-Xcode translator. Nothing more, nothing less. This relationship is what allows us to sprinkle our Javascript layer — React — on top of our native code, and subsequently program (almost) anything without worrying about native specifics.

Much like translating languages in real life — in our case, passing instructions from one programming language to another at run time — can prove challenging. The tool to pass instruction from one programming language to another are events. Events are reasonably easy to generalize and make language-independent. This empowers us to communicate in a variety of situations:

from native to Javascript

from Javascript to native*

from Javascript to Javascript (why not!)

Methods to leverage this powerful tool are described in the advanced React Native documentation… but what the documentation does not tell you is what is happening behind the scenes.

*via LifeCycle events, which will not be covered in this article. More information about them can be found here.

The Almighty Device Event Emitter

We will start our exploration with the most commonly exposed way to pass events from native to Javascript, namely the DeviceEventEmitter . But what is that DeviceEventEmitter exactly?

As the name seems to suggest, the DeviceEventEmitter is a simple, boring event emitter. You could implement it in Javascript or any other language you like. For those not familiar with what an event emitter is, you can take a look at this blog post or at the official documentation for the standard implementation in Node.js. In short, an event emitter is an object that implements:

A listenerStore object containing arrays of listeners, functions that will be called when the matching event type is dispatched. Each of the arrays is associated with an eventName key name indicating the event type to match.

object containing arrays of listeners, functions that will be called when the matching event type is dispatched. Each of the arrays is associated with an key name indicating the event type to match. An addListener(eventName, listener) method that pushes listener to the listenerStore[eventName] .

method that pushes to the . An emit(eventName[,data) method that will loop through the listenerStore[eventName] array and call every listener with the data argument(s).

Typically the listenerStore is an attribute of a specific event emitter. This means that if you have two event emitters, emitterA and emitterB , a listener for myEvent registered on emitterA will not be called on emitterB.emit('myEvent') . This is a little bit problematic because React Native needs to be able to share that listenerStore globally. If it were unable to share the store globally, events triggered at arbitrary locations in your app would not have the ability to reach the appropriate listener.

Let’s take a minute to consider the typical path an event must travel, starting with its origin and ending at its final listener. Most of the events that occur during an application’s lifetime — such as touch or scroll events — are born on the device itself in native code. Yet, event handlers are scattered all over the React portion of the app in Javascript space. An event first needs to leave its birthplace in the “Javascript world” and somehow echo across the entire Javascript codebase so that a listener can catch it, regardless of where that listener may be. How is that technically possible?

For the next step, the idea is fairly simple: unlike most of the modules you can import from ‘react-native’ , DeviceEventEmitter is not a class, but an instance (an object, if you will). Consequently, each import actually gives you a pointer to the very same object which makes it easy to understand why subscriptions are naturally shared across all the Javascript code. What can be a little more difficult to figure out is how that DeviceEventEmitter is able to interact with the native side of the code.

We have learned that once events reach the DeviceEventEmitter , it is easy to dispatch them anywhere in the Javascript code. However, events still need to be able to reach that DeviceEventEmitter — a basic Javascript object and not a class— in the first place. This means that the native code also needs a reference to it. The fact that DeviceEventEmitter is a unique, frozen object is precisely what makes such a feat possible. To understand why, we need to take a closer look at the heart of React Native: the bridge.

If React Native Was an Archipelago…

If React native was a geographical area, it would probably be a little archipelago of three islands linked by two bridges.

— by Claire Couvrat, used with permission

The central (and biggest) island is JS Island. JS Island has two bridges, linking it to Java Island and Xcode Island. Depending on the days (or on the device React Native is running on), only one of these bridges can be used at a given time, even though both still exist.

All of the islands have their own complex highway system. And to make it worse, citizens of these islands don’t even drive on the same side of the road! Before building the bridges, people from the islands agreed on three things:

The two bridges would have a limited number of traffic lanes (to be fair, this was more of a physical constraint than a political agreement…),

(to be fair, this was more of a physical constraint than a political agreement…), To avoid confusion, it would be forbidden to change lanes while on one bridge. If you start in lane A on one island, you will must finish in lane A on the other island

If you start in lane A on one island, you will must finish in lane A on the other island To make it easier for tourists coming from JS Island to visit neighboring islands, both bridges would have exactly the same number of lanes, and these lanes would have the same names — at least on JS Island.