the evolution of redux action creators

Post on 13-Jan-2017

101 Views

Category:

Software

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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

top related