Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

The evolution of redux action creators

437 views

Published on

From redux-thunk to redux-saga middleware

Published in: Software
  • Be the first to comment

The evolution of redux action creators

  1. 1. THE EVOLUTION OF REDUX ACTION CREATORS GEORGE BUKHANOV @NothernEyes northerneyes
  2. 2. Redux is a predictable state container for JavaScript apps.
  3. 3. Action is just a plain object { type: ADD_TODO, text: 'Build my first Redux app' }
  4. 4. Reducer is a pure function function todoApp(state = initialState, action) { switch (action.type) { case ADD_TODO: return Object.assign({}, state, { todos: [ ...state.todos, { text: action.text, completed: false } ] }) default: return state } }
  5. 5. WHAT IS ACTION CREATOR?
  6. 6. Action creator function addTodo(text) { return { type: ADD_TODO, text } }
  7. 7. WHAT ABOUT ASYNC ACTIONS?
  8. 8. It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. REDUX MIDDLEWARE
  9. 9. Redux-thunk export default function thunkMiddleware({ dispatch, getState }) { return next => action => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action); }; }
  10. 10. Action creator with redux-thunk function increment() { return { type: INCREMENT_COUNTER }; } function incrementAsync() { return dispatch => { setTimeout(() => { // Yay! Can invoke sync or async actions with `dispatch` dispatch(increment()); }, 1000); }; }
  11. 11. WHAT ABOUT TESTING?
  12. 12. OUR ACTION CREATORS ARE NOT PURE FUNCTIONS
  13. 13. THE ANSWER IS DEPENDENCY INJECTION
  14. 14. Middleware with dependency injection export default function injectDependencies(dependencies) { return ({dispatch, getState}) => next => action => { if (typeof action !== 'function') return next(action); return action({dispatch, getState, ...dependencies}); }; }
  15. 15. Action creator with DI export function registration(data) { return ({dispatch, api, history, analytics, cookie}) => { dispatch({type: authConstants.REGISTRATION_PENDING}); return api.register(data).then(res => { updateAnalytics(analytics, res, true); saveUserCookie(res, cookie); analytics.track('Registration started'); dispatch({type: authConstants.REGISTRATION_SUCCESS}); const link = '/search'; history.push(link); }).catch(onError(authConstants.REGISTRATION_ERROR, dispatch)); }; }
  16. 16. YEAH! THEY ARE PURE FUNCTIONS
  17. 17. BUT IT IS NOT ENOUGH, WHY?
  18. 18. Tests are still complecated We have some mess in components etc... function() { updateSearchPage()({dispatch, getState: buildState(), api, cookie}); expect(dispatch.calledOnce).to.be.true; expect(calledWithActions( dispatch.getCall(0).args, APPLIED_FILTERS_CHANGED, GET_NOTICES_SUCCESS )).to.be.true; }; function onHandlePress () { this.props.dispatch({type: 'SHOW_WAITING_MODAL'}) this.props.dispatch(createRequest()) } REDUX-SAGA
  19. 19. The most elegant way to write complecated action creators Look at this beautiful code export function* authFlow() { while(true) { yield take(USER_AUTH_CHECK); yield fork(authenticate); const {user, token} = yield take(USER_AUTH_SUCCESS); Session.save(user, auth); yield put(redirectTo('/')); const action = yield take(USER_SIGN_OUT); Session.clear(); yield put(redirectTo('/')); } }
  20. 20. HOW IT WORKS?
  21. 21. GENERATORS
  22. 22. Generators are Functions with bene ts. function* idMaker(){ var index = 0; while(true) yield index++; } var gen = idMaker(); console.log(gen.next().value); // 0 console.log(gen.next().value); // 1 console.log(gen.next().value); // 2
  23. 23. co - generator based control ow var fn = co.wrap(function* (val) { return yield Promise.resolve(val); }); fn(true).then(function (val) { });
  24. 24. LET'S GET BACK TO REDUX-SAGA
  25. 25. Simple example What happens here? select part of the state call the api method put an action export function* checkout() { try { const cart = yield select(getCart); yield call(api.buyProducts, cart); yield put(actions.checkoutSuccess(cart)); } catch(error) { yield put(actions.checkoutFailure(error)); } }
  26. 26. Easy to test test('checkout Saga test', function (t) { const generator = checkout() let next = generator.next() t.deepEqual(next.value, select(getCart), "must select getCart" ) next = generator.next(cart) t.deepEqual(next.value, call(api.buyProducts, cart), "must call api.buyProducts(cart)" ) next = generator.next() t.deepEqual(next.value, put(actions.checkoutSuccess(cart)), "must yield actions.checkoutSuccess(cart)" ) t.end() })
  27. 27. SAGAS CAN BE DAEMONS
  28. 28. Endless loop, is listenertake export function* watchCheckout() { while(true) { yield take(actions.CHECKOUT_REQUEST) yield call(checkout) } }
  29. 29. Root Saga export default function* root() { yield [ fork(watchCheckout) ] }
  30. 30. REDUX-SAGA PATTERNS
  31. 31. can be useful to handle AJAX requests where we want to only have the response to the latest request. takeLatest function* takeLatest(pattern, saga, ...args) { let lastTask while(true) { const action = yield take(pattern) if(lastTask) // cancel is no-op if the task has alerady terminated yield cancel(lastTask) lastTask = yield fork(saga, ...args.concat(action)) } }
  32. 32. allows multiple saga tasks to be forked concurrently. takeEvery function* takeEvery(pattern, saga, ...args) { while (true) { const action = yield take(pattern) yield fork(saga, ...args.concat(action)) } }
  33. 33. FIN @NothernEyes northerneyes

×