Search code examples
reduxreact-reduxredux-thunk

Synchronous Redux Actions


I am trying to make synchronous dispatch calls in Redux where the first call updates a isLoading state so that we can show a loading icon on the UI and then the second call does a lot of synchronous calculations behind the scenes to update item positions - takes about 2-5 seconds.

The first action happens and the state is changed correctly, but it appears that immediately after that happens it doesn't hit the react front-end components, but it goes to the second dispatch. It works when I add in a short timeout after the first dispatch, but I hate to hardcode a fixed wait time.

Is there a better solution to this issue?

NOT WORKING:

const showLoadingIcon = () => ({type: types.SHOW_LOADING_ICON});
export const updateInteractionTypeScores = (updatedValues) => dispatch => {
    dispatch(showLoadingIcon());
    dispatch({type: types.UPDATE_VALUES, updatedValues});
};

WORKING:

const showLoadingIcon = () => ({type: types.SHOW_LOADING_ICON});
export const updateInteractionTypeScores = (updatedValues) => dispatch => {
    dispatch(showLoadingIcon());
    setTimeout(() => dispatch({type: types.UPDATE_VALUES, updatedValues}), 100);
};

Solution

  • What you call a synchronous action really is an asynchronous action, with updates of the store at specific times. The general pattern for things like that is to have (at least) three dispatches for each of the states of the action.

    1. Asynchronous action is launched
    2. Asynchronous action succeeded
    3. Asynchronous action failed

    Launching the asynchronous action dispatches a "ASYNC_ACTION_LAUNCHED" action, which updates the store, to which your components are correctly connected (right?) hence will show the loading icon.

    Upon success, an "ASYNC_ACTION_SUCCESS" is dispatched, the store is updated, the component rerenders with the new store content.

    Upon failure, an "ASYNC_ACTION_FAILURE" is dispatched, the store is updated, the component rerender with empty content and an error message.

    In practice, this amounts to more code, but this allows for much more possibilities:

    const asyncActionLaunched = () => ({type: types.ASYNC_ACTION_STARTED});
    const asyncActionSuccess = () => ({type: types.ASYNC_ACTION_SUCCESS});
    const asyncActionFailed = () => ({type: types.ASYNC_ACTION_FAILED});
    
    export const updateInteractionTypes = dispatch => {
        dispatch(asyncActionLaunched());
    
        // I don't know your setup
        // But this whould either be a Promise, or return a boolean
    
        // in the case of a synchronous function
        const result = makeSynchronousCalculations();
        if (result) {
            dispatch(asyncActionSuccess());
            // dispatch another action that updates store
        } else {
            dispatch(asyncActionFailed());
            // dispatch another action that updates store
        }
        // in the case of a promise
        asyncBlockingAction()
            .then(
                dispatch(asyncActionSuccess());
                // dispatch another action that updates store
            ).catch(
                dispatch(asyncActionFailed());
                // dispatch another action that updates store
            );
    };