artsy / react-redux-controller

Library for creating a controller layer to link React and Redux, on top of react-redux.
MIT License
97 stars 10 forks source link

how to mark controller's method as "blocking"?! #11

Open plandem opened 7 years ago

plandem commented 7 years ago

Required: somehow inside of controller without a lot of boilerplate code to get next: at begin of method dispatch action with promise for promise-middleware, that will be resolved/rejected at the end of method.

Recap: 1) at begin of method set some redux state to true 2) at end of method set same redux state to false 3) at error do same as at (2)

In most cases it's common loading flag (e.g. API requests) to make UI to response changes of state (e.g. block/unblock controls).

Right now I'm doing it like this, but not sure that it's good way, maybe I miss something:

helper:

const blocking = (type, method, dispatch) => (...args)  => {
    return dispatch({ type, payload: new Promise((resolve, reject) => {
        try {
            resolve(method(...args));
        } catch (e) {
            reject(e);
        }
    })});
};

generators with marked onLogin as blocking:

const generators = {
    *initialize() {
        const { dispatch } = yield getProps;

        this.onLogin = blocking(types.APP_PREFIX, this.onLogin, dispatch);

        yield this.userInfo(true, '/');
    },

    *onSiderCollapse() {
        const { dispatch } = yield getProps;
        dispatch(actions.siderCollapse());
    },

    *onLogin(params) {
        const { dispatch } = yield getProps;

            try {
                yield api.login(params);
                yield this.userInfo(false, '/');
            } catch(e) {
                dispatch(error(e)),
                dispatch(auth.reset());
            }
    },

    *onLogout() {
        const { dispatch } = yield getProps;

        try {
            yield api.logout();
        } catch(e) {
        }

        dispatch(auth.reset());
        dispatch(navigate('/'));
    },

    *userInfo(silent = false, returnUrl) {
        const { dispatch, route } = yield getProps;

        try {
            dispatch(auth.request());
            const identity = yield api.fetchCurrrentUser();
            dispatch(auth.receive(identity));

            //if route has url to return, then use it
            let redirect = route && route.query && route.query.return;
            if(!redirect) {
                redirect = returnUrl;
            }

            if(redirect) {
                dispatch(navigate(redirect));
            }
        } catch(e) {
            console.log(e);

            if(!silent) {
                dispatch(error(e));
            }

            dispatch(auth.reset());
            dispatch(navigate('/'));
        }
    }
};

I need it for my reducer, e.g.:

import typeToReducer from 'type-to-reducer';
import * as loading from '../../utils/loading';
import types from './actions';

const initialState = {
    checking: false,
    siderCollapsed: false,
};

const app = typeToReducer({
    [types.SIDER_COLLAPSE]: (state, action) => ({ ...state, siderCollapsed: !state.siderCollapsed }),
    [types.APP_PREFIX]: loading.reducers('checking'),
}, initialState);

export default app;

utils is like this:

export const promiseTypeSuffixes = ['pending', 'fulfilled', 'rejected'];

export const reducers = (key, suffixes = promiseTypeSuffixes) => {
    const reducers = { };
    suffixes.forEach((suffix, i) => reducers[suffix] = (state, action) => ({ ...state, [key]: !i }));
    return reducers;
};
acjay commented 7 years ago

I'm not 100% sure what you're going for, but the idea of RRC was to prevent the need for async middleware.

Is the issue that you're trying to essentially apply a decorator (blocking) to a controller method, but that decorator needs a reference to dispatch, but you can only get a handle to it in initialize? Would it also be possible to make blocking a controller generator method?

plandem commented 7 years ago

in two words, I need to decrease the number of boilerplate code for next behavior:

  1. at start of action - change special state to true (i.e. attach "spin" till "heavy action" in process)
  2. do action in SYNC way
  3. at end of action - change same special state to false

p.s.: yes, some kind of decorator, that dispatches an action at start and before end of action.