Sagas

This is actually

  1. A design pattern instead of a library specified for redux
    1. Even redux is also just a design pattern, it is not something unique for React
  2. Widely used in system design, especially for microservice
    1. http://microservices.io/patterns/data/saga.html

Back to React, practically, we should use stateless component instead of stateful component, local state is not preferred in data-driven architecture, and theoretically, functional programming should never have state.

So, instead of componentDidMount or componentWillMount , if a page required API response prior to UI display, we should use side-effect from saga to control both navigation flow and API call flow. You may pick one of the following three ways for your implementation

Reference for pessimistic approach - Saga Basic

// container
import { bindActionCreators as bind } from 'react-redux';

mapDispatchToProps: (dispatch) => {
    openPurchaseDetail: bind(openPurchaseDetail, dispatch)
}

// presenter
onClick: () => {
    openPurchaseDetail(purchase_id)
}

// saga
yield fork(watchPurchaseDetailStartSaga);

function* watchPurchaseDetailStartSaga(){
    while(true){
        const { purchase_id } = yield take(PURCHASE_DETAIL_START);
        yield put({ type: LOADING }) // optional if you have global loading indicator in Root router
        const response = yield call(API.fetchPurchaseDetail, purchase_id)
        yield put({ type: PURCHASE_DETAIL_FETCHED, response, purchase_id});
        yield call(history.goto, `/myaccount/purchasedetail/${purchase_id}`)
    }
}

Reference for optimistic approach - Saga Advance

// ./app/reducers/AccountViewer.js

case PURCHASE_DETAIL_OPEN: 
{
    return {
        selected_id: action.purchase_id,
        optimistic: action.abstract
    }
}
case PURCHASE_DETAIL_FETCHED:
{
    return {
        ...state,
        selected_id: action.purchase_id,
        fetched: action.response.purchase_detail
    }
}

// ./app/sagas/AccountViewer.js
yield fork(watchPurchaseDetailStartSaga);

function* watchPurchaseDetailStartSaga(){
    while(true){
        const { purchase_id } = yield take(PURCHASE_DETAIL_START);
        yield call(history.goto, `/myaccount/purchasedetail/${purchase_id}`)
        const response = yield call(API.fetchPurchaseDetail, purchase_id)
        yield put({PURCHASE_DETAIL_FETCHED, response, purchase_id});
    }
}

Reference for optimistic approach with interrupt - Saga Advance

// break into watcher and subroutine, let them race
yield fork(watchPurchaseDetailStartSaga);

function* watchPurchaseDetailStartSaga(){
    while(true){
        const { purchase_id } = yield take(PURCHASE_DETAIL_START);
        yield call(history.goto, `/myaccount/purchasedetail/${purchase_id}`)
        yield race({
            response: yield call(fetchPurchaseDetail),
            back: yield take(NAVIGATION_BACK)
        })
    }
}

function* fetchPurchaseDetail(){
    const response = yield call(API.fetchPurchaseDetail, purchase_id);
    yield put({PURCHASE_DETAIL_FETCHED, response, purchase_id});
}

Design principle

  1. Use optimistic update for
    1. InstantUiUpdate-case like upload an image to Telegram, bookmark a programme on Netflix
    2. When user finished editing his user profile, and click "Save", AppState is updated prior to API call
    3. Alert is only available when API returns failure, yet, in some case, we may even suppress it
  2. Use pessimistic update for
    1. OnlyAfterSuccess-case, MissionCritical-case like payment or transaction
    2. When user is paying for his ticket, transaction result must be returned prior to UI update
    3. Success is only displayed upon positive response from API, yet, in most case, timeout may regarded as failure
  3. Framework like MeteorJS etc provides a fancy OptimisticUpdater layer wrapping the store, always utilise those things if available.
  4. You can make your own optimistic layer using nested reducers

Reference

// ./app/reducers/AccountViewer.js
/*
AccountViewer:
    purchase_detail:
        selected_id: 'f34r83r34'
        fetched:
            date: 23421231234
            items:
                - title
                  cost
            total: 234
        optimistic:
            date: 23421231234
            total: 234
*/
// ./app/selectors/AccountViewer.js

getCurrentPurchaseDetail = ({purchase_detail}) => purchase_detail.optimistic.merge(purchase_detail.fetched);
isPurchaseDetailOptimistic = ({purchase_detail}) => !purchase_detail.fetched;

results matching ""

    No results matching ""