The evolution of redux action creators
-
Upload
george-bukhanov -
Category
Software
-
view
101 -
download
2
Transcript of The evolution of redux action creators
THE EVOLUTION OF REDUX ACTION CREATORS
GEORGE BUKHANOV
@NothernEyesnortherneyes
Redux is a predictable state container for JavaScript apps.
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 dispatchingan 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())}
The most elegant way to write complecated action creators
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); // 0console.log(gen.next().value); // 1console.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 happenshere?
select part of the statecall the api methodput 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 wewant 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 forkedconcurrently.
takeEvery
function* takeEvery(pattern, saga, ...args) { while (true) { const action = yield take(pattern) yield fork(saga, ...args.concat(action)) }}
FIN
@NothernEyesnortherneyes