Since in the latest redux-observable (0.17) is direct calling of store.dispatch() deprecated, I wonder what is an alternative if I need to dispatch actions from the outside of my redux app.
Example:
Let's say I have this function which initialize native module and set up native handler.
const configure = (dispatch) => {
const printingModule = NativeModules.PrintingManager
const eventEmitter = new NativeEventEmitter(printingModule)
eventEmitter.addListener(
"PrintingManagerNewPrinterConnected",
(payload) => dispatch({
type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
payload: {
macAddress: payload[2],
connectionType: payload[3],
},
}))
printingModule.initialize()
}
What I typically do is that I will call this function from observable after something like APP_STARTUP_FINISHED:
const appStatePrepared = (action$: Object, { dispatch }) =>
action$.ofType(APP_STATE_PREPARED)
.switchMap(() => {
configurePrinters(dispatch)
})
What is the correct solution for this?
Thanks!
When using RxJS the ideal is to compose streams. So in this case we need to some how create a stream of "PrintingManagerNewPrinterConnected"
events that we can then map each to their own PRINTER_MANAGER_NEW_PRINTER_CONNECTED
action.a
Let's first learn how to do this completely custom.
Creating your own custom Observables is very similar to creating a Promise. So say you had the most simple Promise in the world that just immediately resolves to the number 1
const items = new Promise(resolve => {
resolve(1);
});
The equivalent Observable looks super similar
const items = new Observable(observer => {
observer.next(1);
observer.complete();
});
Visually, the main differences are that instead of being passed (resolve, reject)
callbacks we're given an Observer because there is next, error, and complete.
Semantically, Observables can represent more than one value by calling observer.next
as many times as they'd like until they call observer.complete()
to signal the end of the stream; this is in contrast to Promises, which only represent a single value.
Observables are also lazy and synchronous by default, whereas Promises are always eager and async.
Now that we have that understanding we want to take that and wrap your NativeEventEmitter
API which uses addEventListener
.
const configurePrinters = () => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
eventEmitter.addListener(
'PrintingManagerNewPrinterConnected',
(payload) => observer.next(payload)
);
printingModule.initialize();
});
};
configurePrinters()
.subscribe(payload => console.log(payload));
This works and is super simple, but there's one problem with it: we should call removeListener
when they unsubscribe so that we clean up after ourselves and don't leak memory.
To do that, we need to return a subscription inside our custom Observable. A subscription in this context is an object that has an unsubscribe()
method on it, which will be called automatically when the subscriber unsubscribes, an error is triggered, or your observable completes. This is your chance to clean up.
const items = new Observable(observer => {
let i = 0;
const timer = setInterval(() => {
observer.next(i++);
}, 1000);
// return a subscription that has our timer cleanup logic
return {
unsubscribe: () => {
clearInterval(timer);
}
};
});
Because returning an object is a bit verbose RxJS supports a shorthand where you just return a function which itself will be treated as the unsubscribe
method.
const items = new Observable(observer => {
let i = 0;
const timer = setInterval(() => {
observer.next(i++);
}, 1000);
// return an "unsubscribe" function that has our timer cleanup logic
return () => {
clearInterval(timer);
};
});
Now we can apply this to our example, where we want to remove our listener when our unsubscribe teardown function is called.
const configurePrinters = () => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
const listener = (payload) => observer.next(payload);
eventEmitter.addListener(
'PrintingManagerNewPrinterConnected',
listener
);
printingModule.initialize();
return () => eventEmitter.removeListener(
'PrintingManagerNewPrinterConnected',
listener
);
});
};
Now let's turn this into a reusable utility function
const fromPrinterEvent = (eventName) => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
const listener = (payload) => observer.next(payload);
eventEmitter.addListener(eventName, listener);
printingModule.initialize();
return () => eventEmitter.removeListener(eventName, listener);
});
};
fromPrinterEvent('PrintingManagerNewPrinterConnected')
.subscribe(payload => console.log(payload));
While NativeEventEmitter
is a react-native thing, it follows the node-style EventEmitter interface and RxJS already comes with a utility helper to create an Observable from them to save you the effort. It's called fromEvent
, found at Observable.fromEvent
or import { fromEvent } from 'rxjs/observables/fromEvent'
.
const fromPrinterEvent = (eventName) => {
return Observable.defer(() => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();
return Observable.fromEvent(eventEmitter, eventName);
});
};
Here I also wrapped it in Observable.defer
so that we don't create the NativeEventEmitter
or printingModule.initialize()
until someone actually subscribes (maintain laziness). This may or may not be neccesary for you, I don't know what PrintingManager does or how it behaves. e.g. it might be desirable to only create a single emitter and to initialize the module upfront.
const printingModule = NativeModules.PrintingManager;
const printerEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();
const fromPrinterEvent = (eventName) =>
Observable.fromEvent(printerEmitter, eventName);
So keep in mind that I'm just showing patterns without knowing what PrintingManager, etc does.
To use this within redux-observable and your epic is now the same as you'd use any other Observable. So we'll want to map the values from it to actions and mergeMap, switchMap, concatMap, or exhaustMap that into our top-level stream.
Something like this:
const appStatePrepared = action$ =>
action$.ofType(APP_STATE_PREPARED)
.switchMap(() =>
fromPrinterEvent('PrintingManagerNewPrinterConnected')
.map(payload => ({
type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
payload: {
macAddress: payload[2],
connectionType: payload[3],
}
}))
);
Remember that many streams, including our custom fromPrinterEvent('PrintingManagerNewPrinterConnected')
, go forever until you unsubscribe from them. So if you only want one you'd use .take(1)
. If you want to unsubscribe when you receive another action you'd use .takeUntil(action$.ofType(WHATEVER))
, etc. Normal RxJS patterns.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With