Async Redux Actions With RxJS - React Rally 2016
-
Upload
ben-lesh -
Category
Technology
-
view
1.882 -
download
0
Transcript of Async Redux Actions With RxJS - React Rally 2016
PowerPoint Presentation
Async Redux Actions With RxJS a love story. stateSometimes it returns a new state(state, action) => state + action.value
Remove mutation?8
Reducers in Reduxconst reducer = (state = { value: 0 }, action) => { switch (action.type) { case INCREMENT: return { value: state.value + 1 }; case DECREMENT: return { value: state.value 1 }; default: return state; }};
Reducers are great for managing state!An action notifies you of a change, you return a new state! So easy!
Redux reducers handle state transitions, but they must be handled synchronously
But what about async?User interactions (mouse, keyboard, etc)AJAXWeb SocketsAnimationsWorkers, et al
A LOT of async resultscan be handled synchronouslyClick a button and update a valueSelect an option and update a valueGet a single AJAX request and update viewMouse moves updating some coordinates
Well, some async is harder than othersAJAX cancellationComposed AJAXDebounced form submissionsDrag and dropAdvanced web socket use
What do these harder async stories have in common?Composing multiple async sources.and cancellation!.
In redux, we use middleware to manage async
Most redux middlewares use callbacks or promises
CallbacksThe most primitive way to handle asynchrony in JavaScript is with callbacks
getSomeData((data) => { dispatch({ type: I_HAVE_DATA, data });});
Callback Hell(aka the flying V)getSomeData(id, (data) => { dispatch({ type: SOME_DATA, data }); getSomeData(data.parentId, (parent) => { dispatch({ type: MORE_DATA, data }); getSomeData(parent.parentId, (grandparent) => { dispatch({ type: DATAX3_YOLO_LOL, data }); }); });});
Have a redux tie-in (dispatch instead of doStuff)
note threading of errors19
Promises provide a cleaner solutiongetSomeData(id) .then(data => { dispatch({ type: SOME_DATA, data }); return getSomeData(data.parentId); }) .then(data => { dispatch({ type: MORE_DATA, data }); return getSomeData(data.parentId); }) .then(data => { dispatch({ type: DATAX3_YOLO_LOL, data }); })
PromisesGuaranteed FutureImmutableSingle ValueCachingThese two features can be problematicin modern web applications
Promises cant be cancelledThis means youre executing code you dont want to
Why cancellation is importantAuto-completeChanging views/routes before data finishes loadingTearing down resources
Loading view data without cancellation
DaredevilLoading view data without cancellation
DaredevilThe Get DownSince you cant cancel the previous promise,youre stuck processing the responseand somehow signaling disinterestLoading view data without cancellation
Heres Daredevil!
DaredevilA better scenario
DaredevilThe Get Down
Ideally, when we make a new requestwe can abort the the old oneso its never handled and processedA better scenario
Thanks, Kent C Dodds!
But these days resources are cheap, right?Netflix targets devices that are 465x slower than your laptop
Times are changingLaptops and desktops are very fastTabletsSmartphonesIndustrial devicesSmartTVsAppliancesWearables
Promises are a single value
Which of these are single value?User interactions (mouse, keyboard, etc)AJAXWeb SocketsAnimationsWorkers, et al
What do we use?
ObservablesA set of eventsAny number of valuesOver any amount of timeCancellableLazy
RxJSObservables and functions to create and compose Observables
RxJSLodash for async
RxJS provides many ways to create Observablesinterval(1000)fromEvent(button, click)from([1, 2, 3, 4]);of(hello);ajax.getJSON(http://example.com);webSocket(ws://echo.websocket.com);many, many more
Subscribing to an observablemyObservable.subscribe(x => console.log(x));
Subscribing to an observablemyObservable.subscribe( x => console.log(x), err => console.error(err));
Subscribing to an observablemyObservable.subscribe( x => console.log(x), err => console.error(err) , () => console.info(complete));
Subscribing to an observableconst subscription = myObservable.subscribe( x => console.log(x), err => console.error(err) , () => console.info(complete));
subscription.unsubscribe();
We have sets of events that we can cancel!Okay, now what?
Note promises are only 1 event, this is 0-N.43
Sets can be transformedmap, filter, reduce
Sets can be combinedconcat, merge, zip
Observables are sets with another dimension: TIMEbuffer, throttle, debounce, combineLatest
Observables are lazy so they can be resubscribedretry, repeat
There is an error path, so we can catch, just like promisemyObservable.catch(err => Observable.of(handled))
You can do just about anything with RxJS and Observables!
- redux documentation
WellRedux has amazing tooling, community and support around it.
Redux with middlewareprovides solid architecture patterns
Async in redux without middleware
Reducerinstance statehandler kicking off asyncrenderComponent
Async in redux with middleware(and react-redux)
ReducerEpicStateless Component
Lets combine RxJS and redux!
redux-observableEpic middleware for redux
Whats an Epic?A function that takes a stream of all actions dispatched, and returns a stream of actions to dispatch.
const pingPongEpic = (action$, store) => action$.ofType(PING) .map(action => ({ type: PONG });
Whats an Epic?Actions in, actions out
const pingPongEpic = (action$, store) => action$.ofType(PING) .map(action => ({ type: PONG });
Basic middleware setupimport { createStore, applyMiddlware } from redux;import { createEpicMiddleware } from redux-observable;import reducerFn, { epicFn } from ./redux/updown;
const epicMiddleware = createEpicMiddleware(epicFn);
const store = createStore( reducerFn, applyMiddleware(epicMiddleware));
Idiomatic redux increment decrement with added debounceconst upEpic = (action$, store) => action$.ofType(UP) .debounceTime(1000) .map(() => ({ type: INCREMENT }));
const downEpic = (action$, store) => action$.ofType(DOWN) .debounceTime(1000) .map(() => ({ type: DECREMENT }));
export const updownEpic = combineEpics(upEpic, downEpic);
Idiomatic redux increment decrement with added debounceexport default const updown = (state = { value: 0 }, action) => { switch (action.type) { case INCREMENT: return { value: state.value + 1 }; case DECREMENT: return { value: state.value 1 }; default: return state; }};
Idiomatic redux increment decrement with added debounce
WARNING: Dont read all of this
Auto-Complete Plain JS
Auto-Complete Epicexport const autoCompleteEpic = (action$, store) => action$.ofType(LOAD_QUERY) .debounceTime(500) .switchMap(action => ajax.getJSON(`http://endpoint/q=${action.value}`) .map(results => ({ type: QUERY_RESULTS, results })) );
compose in cancellation via dispatched actionsexport const autoCompleteEpic = (action$, store) => action$.ofType(LOAD_QUERY) .debounceTime(500) .switchMap(action => ajax.getJSON(`http://endpoint/q=${action.value}`) .map(results => ({ type: QUERY_RESULTS, results })) .takeUntil(action$.ofType(CANCEL_QUERY)) );
Multiplexed Socket Plain JS
Multiplexed Socket Epic
const socket = new WebSocketSubject('ws://stock/endpoint');
const stockTickerEpic = (action$, store) => action$.ofType('GET_TICKER_STREAM') .mergeMap(action => socket.multiplex( () => ({ sub: action.ticker }), () => ({ unsub: action.ticker }), msg => msg.ticker === action.ticker ) .retryWhen( err => window.navigator.onLine ? Observable.timer(1000) : Observable.fromEvent(window, 'online') ) .takeUntil( action$.ofType('CLOSE_TICKER_STREAM') .filter(closeAction => closeAction.ticker === action.ticker) ) .map(tick => ({ type: 'TICKER_TICK', tick })) );
The GoodMakes it very easy to compose and control complex async tasks with RxJS and reduxCan use redux toolingYou dont end up managing your own Rx subscriptionsIf used with react-redux, makes all of your components stateless
The BadNeed to know redux in advanceShould learn RxJS in advanceRxJS has a bit of a learning curve
redux-observablehttps://github.com/redux-observable/redux-observable
Co-Author Jay Phelps
Twitter: @_jayphelpsGithub: jayphelps
Thank you!
@benlesh