Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
THE EVOLUTION
OF REDUX ACTION CREATORS
GEORGE BUKHANOV
@NothernEyes
northerneyes
The evolution of redux action creators
The evolution of redux action creators
Redux is a predictable state container for JavaScript apps.
The evolution of redux action creators
Action is just a plain object
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
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
}
}
WHAT IS ACTION CREATOR?
Action creator
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
WHAT ABOUT ASYNC ACTIONS?
It provides a third-party extension point between dispatching
an action, and the moment it reaches the reducer.
REDUX MIDDLEWARE
Redux-thunk
export default function thunkMiddleware({ dispatch, getState }) {
return next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
}
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);
};
}
WHAT ABOUT TESTING?
OUR ACTION CREATORS ARE NOT PURE FUNCTIONS
THE ANSWER IS
DEPENDENCY INJECTION
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});
};
}
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));
};
}
YEAH! THEY ARE PURE FUNCTIONS
BUT IT IS NOT ENOUGH, WHY?
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
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('/'));
}
}
HOW IT WORKS?
GENERATORS
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
co - generator based control ow
var fn = co.wrap(function* (val) {
return yield Promise.resolve(val);
});
fn(true).then(function (val) {
});
LET'S GET BACK TO REDUX-SAGA
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));
}
}
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()
})
SAGAS CAN BE DAEMONS
Endless loop, is listenertake
export function* watchCheckout() {
while(true) {
yield take(actions.CHECKOUT_REQUEST)
yield call(checkout)
}
}
Root Saga
export default function* root() {
yield [
fork(watchCheckout)
]
}
REDUX-SAGA PATTERNS
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))
}
}
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))
}
}
FIN
@NothernEyes
northerneyes

More Related Content

The evolution of redux action creators

  • 1. THE EVOLUTION OF REDUX ACTION CREATORS GEORGE BUKHANOV @NothernEyes northerneyes
  • 4. Redux is a predictable state container for JavaScript apps.
  • 6. Action is just a plain object { type: ADD_TODO, text: 'Build my first Redux app' }
  • 7. 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 } }
  • 8. WHAT IS ACTION CREATOR?
  • 9. Action creator function addTodo(text) { return { type: ADD_TODO, text } }
  • 10. WHAT ABOUT ASYNC ACTIONS?
  • 11. It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. REDUX MIDDLEWARE
  • 12. Redux-thunk export default function thunkMiddleware({ dispatch, getState }) { return next => action => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action); }; }
  • 13. 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); }; }
  • 15. OUR ACTION CREATORS ARE NOT PURE FUNCTIONS
  • 17. 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}); }; }
  • 18. 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)); }; }
  • 19. YEAH! THEY ARE PURE FUNCTIONS
  • 20. BUT IT IS NOT ENOUGH, WHY?
  • 21. 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
  • 22. 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('/')); } }
  • 25. 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
  • 26. co - generator based control ow var fn = co.wrap(function* (val) { return yield Promise.resolve(val); }); fn(true).then(function (val) { });
  • 27. LET'S GET BACK TO REDUX-SAGA
  • 28. 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)); } }
  • 29. 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() })
  • 30. SAGAS CAN BE DAEMONS
  • 31. Endless loop, is listenertake export function* watchCheckout() { while(true) { yield take(actions.CHECKOUT_REQUEST) yield call(checkout) } }
  • 32. Root Saga export default function* root() { yield [ fork(watchCheckout) ] }
  • 34. 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)) } }
  • 35. 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)) } }