Reputation: 2248
I am trying to implement an EventEmitter/Subscriber relationship between two components in a react native class. I have seen referenced the following materials:
These solutions are adequate for what I am trying to accomplish, however, they bother require the use of mixins: [Subscribable.Mixin]
on the receiving component to work properly with Subscriber
. Unfortunately, I am using ES6 and extending my classes from Component
so I can not use this mixin syntax.
My question is: How can I implement the above solutions in ES6 without the use of mixins?
Upvotes: 25
Views: 73653
Reputation: 32552
In my idea, I don't think there would be any need to have a third-party library, In the following, you can find a custom solution for any JavaScript/TypeScript environment like Web, React Native or Node.js:
const isFunction = (arg: any): boolean => typeof arg === 'function';
const isString = (arg: any): boolean => typeof arg === 'string';
type CallbackType = (data: unknown) => void;
type AddEventListenerReturnType = () => boolean;
type ListenersType = {
count: number;
refs: Record<string, {name: string; callback: CallbackType}>;
};
class EventRegister {
private static _Listeners: ListenersType = {
count: 0,
refs: {},
};
public addEventListener(
eventName: string,
callback: CallbackType,
): AddEventListenerReturnType {
if (isString(eventName) && isFunction(callback)) {
EventRegister._Listeners.count++;
const eventId = 'el' + EventRegister._Listeners.count;
EventRegister._Listeners.refs[eventId] = {
name: eventName,
callback,
};
return this.removeEventListener.bind(this, eventId);
}
throw new Error('Event name must be a string, Callback must be a function');
}
public removeEventListener(eventId: string): boolean {
if (isString(eventId)) {
return delete EventRegister._Listeners.refs[eventId];
}
throw new Error('Event ID must be a string');
}
public removeAllListeners(): boolean {
let removeError = false;
Object.keys(EventRegister._Listeners.refs).forEach((_id) => {
const removed = delete EventRegister._Listeners.refs[_id];
removeError = !removeError ? !removed : removeError;
});
return !removeError;
}
public emitEvent(eventName: string, data: unknown): void {
Object.keys(EventRegister._Listeners.refs).forEach((_id) => {
if (
EventRegister._Listeners.refs[_id] &&
eventName === EventRegister._Listeners.refs[_id].name
) {
EventRegister._Listeners.refs[_id].callback(data);
}
});
}
}
const EventEmitter = new EventRegister();
export {EventEmitter};
Upvotes: 0
Reputation: 11
With react-native 0.69.0 I solved it like this:
import EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter';
const emitter = new EventEmitter();
emitter.addListener('event name', (...args) => console.log('emitted with', args));
emitter.emit('event name', { message: 'Foo' });
Upvotes: 0
Reputation: 426
This might be a very late answer, but I'm just going to put it out there for anyone who might find this useful.
As of the time of writing this answer (July, 2020), React Native has changed a lot since version 0.60.0+
, you can either use an instance of EventEmitter
, or statically call the DeviceEventEmitter
methods.
Here is an example using EventEmitter
:
import { EventEmitter } from 'events';
const newEvent = new EventEmitter();
// then you can use: "emit", "on", "once", and "off"
newEvent.on('example.event', () => {
// ...
});
Another example using the DeviceEventEmitter
:
import { DeviceEventEmitter } from 'react-native';
// then you can directly use: "emit", "addListener", and "removeAllListeners"
DeviceEventEmitter.emit('example.event', ['foo', 'bar', 'baz']);
Hope that comes handy for anyone who still looking for a way to implement custom events in React Native.
Upvotes: 30
Reputation: 9127
You don't need mixins to use EventEmitters.
Simple demo:
import EventEmitter from 'EventEmitter';
let x = new EventEmitter();
function handler(arg) {
console.log(`event-name has occurred! here is the event data arg=${JSON.stringify(arg)}`);
}
x.addListener('event-name', handler);
x.emit('event-name', { es6rules: true, mixinsAreLame: true });
The full signature for addListener
takes three args:
EventEmitter.addListener(eventName, handler, handlerContext)
In a react component, you likely want to use that context arg, so that the handler can be a class method instead of an inline function and still retain this == component instance
. E.g.:
componentDidMount() {
someEmitter.addListener('awesome', this.handleAwesomeEvents, this);
// the generalist suggests the alternative:
someEmitter.addListener('awesome', this.handleAwesomeEvents.bind(this));
}
handleAwesomeEvents = (event) => {
let awesomeness = event.awesomeRating;
// if you don't provide context in didMount,
// "this" will not refer to the component,
// and this next line will throw
this.setState({ awesomeness });
};
FYI: I got this from looking at the decidedly unmagical implementation of the infamous Subscribable mixin. Google search results are basically an echo chamber of Ramsay's single mixin-based demo.
P.S. As far as exposing this emitter to another component, I'd probably have the owning component provide a function for receiving the emitter reference, and the component that creates the emitter would then conditionally execute that prop with the emitter.
// owner's render method:
<ThingThatEmits
onEmitterReady={(emitter) => this.thingEmitter = emitter}
/>
// inside ThingThatEmits:
componentDidMount() {
this.emitter = new EventEmitter();
if(typeof this.props.onEmitterReady === 'function') {
this.props.onEmitterReady(this.emitter);
}
}
Upvotes: 38
Reputation: 233
I was able to get a workaround with react-mixin. Not sure how proper it is, but it works without any modification. The key is adding reactMixin(DetailView.prototype, Subscribable.Mixin);
after the class definition.
Going off the example that is floating around for EventEmitter and Subscribable:
'use strict';
var reactMixin = require('react-mixin');
var React = require('react-native');
var EventEmitter = require('EventEmitter');
var Subscribable = require('Subscribable');
var {
AppRegistry,
StyleSheet,
Text,
View,
NavigatorIOS
} = React;
class MainView extends Component {
constructor(props){
super(props);
this.EventEmitter = new EventEmitter();
}
somethingHappenedFunction(){
this.EventEmitter.emit("update_event", { message: "hello from up here"});
}
//rest of the class
}
class DetailView extends Component {
componentDidMount(){
this.addListenerOn(this.props.events, 'update_event', this.miscFunction);
}
miscFunction(args) {
console.log("message: %s", args.message);
}
//rest of the class
}
reactMixin(DetailView.prototype, Subscribable.Mixin);
Upvotes: 1