Handling the “possible EventEmitter memory leak detected” Error in React Native

The project that I’m working on is an e-commerce app for an online retailer, Build.com, built using React Native. React Native is a JavaScript framework that allows you to build cross-platform mobile apps in JavaScript that have near-native performance.

While we use Redux to handle the state of our app and its data, occasionally we need to pass real-time messages between the various parts of our app. To do this, we use events, a cross-platform wrapper for NodeJS’s EventEmitter.

A few times, I’ve come across a rather strange error with EventEmitter and I thought I’d share its cause and one possible solution. The error message that I see is:

warning: possible EventEmitter memory leak detected. 2 listeners added. Use emitter.setMaxListeners() to increase limit.

Why does the “possible EventEmitter memory leak” error occur?

I have seen this specific error in a couple situation, I have read about a third, and I can conceptualize a fourth. In both of the situations that I have seen, the EventEmitter listener was being added in componentWillMount, but I’m sure it could easily occur in other places too.

In short, this error occurs when multiple listeners are added to the same event.

In React Native or React, a listener might be set up using code that looks something like this:

componentWillMount() {
  EventEmitter.addListener('myEvent', this.handleMyEvent);
}
 
componentWillUnmount() {
  EventEmitter.removeListener('myEvent', this.handleMyEvent);
}

This code tells React to set up the EventEmitter when the component is mounted (created) and to remove the EventEmitter when the component is unmounted (torn down). The “possible EventEmitter memory leak” error can occur any time addListener runs multiple times without the corresponding removeListener running in between.

Note: since I first began working on this blog post, the React team has released statements indicating that at some future date it is possible that componentWillMount could run multiple times while the component is mounting. Therefore, registering EventEmitters and any other code that should only run once (for example, fetching data from an API) should be run in componentDidMount

As I mentioned before, I have seen this the error in several scenarios:

  • The first was a situation where you could navigate to a page with an event emitter and then go to another version of the same page without removing the first from the scene stack (the React Native router uses a scene stack). Specifically, the description page for a product had the event emitter and you could navigate to the description page for a related product without popping the first one off the stack. Since the first page never unmounted, you ended up with 2 functions attached to the same event. I ended up solving this issue using the code below.
  • The second was a situation where I was actively editing the layout of a page that had an EventEmitter. I was using live reload so each time I made an edit, the page would reload which would trigger componentWillMount and cause another copy of the EventEmitter function to be registered which triggered the error. In that case, I chose not to fix the bug since I determined it wasn’t a realistic problem; however, if you were spending a lot of time editing a page, it could be worth it for your own sanity.
  • The third situation that I’ve read about but not seen myself was where the EventEmitter was attached to a component that was repeated a number of times on a screen. Specifically, I believe the EventEmitter was attached to a link and the link component was iterated over. Without seeing the exact scenario, my first impression is that this is a bad design. Most likely, the same effect could be created using one EventEmitter that watches a bunch of links. However, if it truly can’t be solved any other way, EventEmitter allows you to increase the threshold of registered functions before the error is triggered using EventEmitter.setMaxListeners().
  • The fourth situation did not trigger the error but it has caused me problems. This is where you try to register a fat arrow function as your event handler and then try to remove the listener by removing another fat arrow function:
    componentWillMount() {
      EventEmitter.addListener('myEvent', () => { this.handleMyEvent(); });
    }
     
    componentWillUnmount() {
      EventEmitter.removeListener('myEvent', () => { this.handleMyEvent(); });
    }

    Despite the fact that both fat arrow functions do the same thing and look the same, this code will not work as expected. JavaScript will treat these two functions as different. So the registered listener will not be removed correctly and you could end up with multiple functions registered to the same event. This code was not triggering the “possible EventEmitter memory leak” error when I came across it and I’m guessing that’s because JavaScript treats each fat arrow function as a different function.

How can I fix the “possible EventEmitter memory leak” error?

The solution I found was to simply check for the existence of the EventEmitter before I register the new one. As mentioned above, moving the code to componentDidMount could help prevent some errors down the road.

componentDidMount() {
	if (!EventEmitter.listeners('myEvent').length) {
		EventEmitter.addListener('myEvent', this.handleMyEvent);
	}
}

I think this elegantly solves the issue of multiple listeners for the same event. If you are seeing this error, you may also need to reconsider the architecture of your application and components that allows it to happen in the first place.

2 Comments

  1. Harold Price on December 11, 2019 at 5:55 pm

    When you say “you may also need to reconsider the architecture of your application and components”, do you know of a better way to add, for example, an didFocus event that does not allow this to happen?

    Thanks,

    • Garrett McCullough on February 10, 2020 at 4:21 pm

      Hi Harold,

      Sorry for the late response. I’m not exactly sure what scenario you have in mind but I could take a guess.

      I could see wanting to register an event listener for didFocus that watches for when an input field is focused and does something like scroll it into view. For this sort of scenario, I’d keep the event listener at the form or screen level so that one listener is watching a bunch of input fields. If you need to catch the event at the level of the input field (and there are multiple inputs on the screen) then you could look at an alternate event handler like the onFocus property on the TextInput component. Also, it may be possible to use the onFocus property to consolidate a number of events into a central place like Redux or a context provider

      I hope that helps. Thanks for reading my blog post

Leave a Comment