React Native Events in Gory Details: What Happens on the Way to Listeners

Where we learn about the React native bridge, and realize that React’s central event handling system often gets bypassed…

React native: it’s all about the bridge.

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.
  • An addListener(eventName, listener) method that pushes listener to the listenerStore[eventName].
  • 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 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
  • 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.

After a long negotiation, names were finally given to the lanes of each bridge: RCTDeviceEventEmitter, RCTEventEmitter, RCTLog, Systrace, for a total of 11 lanes per bridge.

React Native’s bridge

In truth, bridging in React Native is slightly more complex, and the number of lanes is not always fixed. However, this analogy still works well because it is exactly what happens under the hood whenever React Native orchestrates the communication between native and Javascript code. For a given bridge (let’s say between Java and Javascript in the case of an Android device), a lane points from one module to its strict equivalent on the other side. Same name, same methods, which is why our DeviceEventEmitter has to be a unique object and not a class. This way it can be called from both Javascript and native, and an event of type myEvent emitted from native will be correctly forwarded to all those who listening to myEvent on the Javascript side.

Code samples

To keep this article at a reasonable length, I will only list the highlights and main steps. Working examples are available on this repository. And you can also jump straight to the conclusion and come back to these later.

From native to Javascript

Note: Examples will therefore be given in Java, since I have more experience with it than with Xcode, but more can be found on the official Xcode webpage. The principles remain the same.

You first need to define your native component. Because our purpose is to show a simple way to emit events, this will not be a UI component but a Native module (yes, they are different) and will therefore be a class extending ReactContextBaseJavaModule. This compels you to implement a getName() and a constructor method. To expose a method to Javascript, you need to decorate it with @ReactMethod, and it must return void . The @ReactMethod decorator basically creates another method returning an array describing the signature (name, arguments, and type of these arguments) of the function. That signature is then communicated to the other end of the bridge (Javascript, in that case).

In order to send events to Javascript, you need to reference the DeviceEventEmitter. This can be done by grabbing the current context, to which the bridge is linked, and then getting the right Javascript module

Finally, create a ReactPackage for your module, and reference it in MainApplication.java. This is essentially boilerplate.

With the above code, we’ve wired an event emitting directly to DeviceEventEmitter. Because of that, the only thing that needs to be done in your Javascript component is importing it and adding the right event listener:

From Javascript to Javascript

No problem here, the only thing that needs to happen is importing DeviceEventEmitter and using emit() and addListener() accordingly.

Note that although it is perfectly possible to use DeviceEventEmitter directly, this is declared as deprecated in the code. Instead, you should use the NativeEventEmitter class as a base pattern and extend it if needed.

Wait, didn’t we say that having one object — and not one class — is what allowed the whole system to work? Do not worry: every instance of NativeEventEmitter gets initialized with a reference to the array of listeners stored in DeviceEventEmitter, so an event emitted on one instance of NativeEventEmitter can be caught by any other instance, and it all works as intended.

Wait, where is my event bubbling ?

So far, what we have uncovered is that all event emitters — be it the unique DeviceEventEmitter or our multiple instances of NativeEventEmitter — share the same subscriber list. While providing incredible flexibility (you do not need to explicitly reference a target), it is important to keep in mind that, if you decide to use events in React native, an event someEvent emitted on any NativeEventEmitter instance or on the DeviceEventEmitter will be forwarded to all listeners for someEvent, no matter where these listeners have been added! Consider the following example, where MyEmitter and MyListener respectively emit and listen for someEvent, each having its own instance of NativeEventEmitter :

Every time a MyEmitter emits an event, both MyListeners will catch it, even if there is no parent-children relationship between them! Beware of unwanted triggers…

At this point, you might have reached the conclusion by yourself: with DeviceEventEmitter, there is no thing such as event bubbling. That’s right, after that lengthy article on the greatness of React’s unified event system, what we have discovered today is that the most mainstream way to forward events between native and Javascript, or even from Javascript to Javascript… does not use bubbling at all.

But React’s bubbling system is great! Can’t I find a way to use it? You can, you can. There is a way — albeit a pretty hacky one. And if you want to discover all about it, then you will have to wait for the part two of this article, coming soon™!

One last thing — you might not need events at all

Events are an essential part of React Native itself. But let’s be honest, as far as standard applications are concerned, what you are looking for might simply be callbacks. React is built around using callbacks — passed from parent to children with props, and this is the easiest and safest way to mimic event-like behavior on the Javascript side. One of the biggest drawbacks of using events is that, once you’ve emitted them, you have no idea of who is on the listening end — meaning you can trigger unwanted actions, and that your code becomes considerably harder to maintain. With callbacks however, this issue disappears, and you are in full control of what is happening.

With that being said, there are times where it could be argued that events are preferable: when you have a component buried so deep under that wiring callbacks all the way through would be a hassle, or when you need to call a component from sub-tree A while being in sub-tree B.

In any case, the purpose of this article was to show that you can use event emitting in React Native. I will leave it to you to decide when you should.


I hope you’ve enjoyed this new trip with me, this time on the React Native side of things! As always, there could have been more details added, especially on the technical details behind the bridge, but I avoided these here to stay centered on the topic of events.

Next time, we will come back to our dear event system, and write our own event plugin! Stay tuned…