Saturday, March 23, 2019

Don't use siloed mapDispatchToProps, use redux-thunk

This makes getLoggedUser functionality not accessible on all components but App component

export const incrementCounter = (n?: number): CounterAction => ({
    type: CounterActionType.COUNTER__INCREMENT,
    n
});

const mapDispatchToProps = (dispatch: Dispatch) => ({
    incrementCounter: (n?: number) => dispatch(incrementCounter(n)),
    getLoggedUser: async () => {
        const userRequest = await fetch('https://jsonplaceholder.typicode.com/users/1');

        const {status} = userRequest;

        if (!(status === 200 || status === 301)) {
            throw new Error('HTTP Status: ' + status);
        }

        const {username} = await userRequest.json() as IUserDto;

        await dispatch(setLoggedUser(username));

        await dispatch(setColorTheme('blue'));

    }
});

export default connect(mapStateToProps, mapDispatchToProps)(hot(App));


To make getLoggedUser functionality accessible from other components, instead of defining it in mapDispatchToProps, define getLoggedUser outside and make it return a thunk that accepts dispatch parameter. A function returned from a function is called a thunk. Include the thunk creator on action creators.


export const incrementCounter = (n?: number): CounterAction => ({
    type: CounterActionType.COUNTER__INCREMENT,
    n
});

export const getLoggedUser = () => async (dispatch: Dispatch): Promise<void> =>
{
    const userRequest = await fetch('https://jsonplaceholder.typicode.com/users/1');

    const {status} = userRequest;

    if (!(status === 200 || status === 301)) {
        throw new Error('HTTP Status: ' + status);
    }

    const {username} = await userRequest.json() as IUserDto;

    await dispatch(setLoggedUser(username));

    await dispatch(setColorTheme('blue'));

};


const actionCreators = {
    incrementCounter, 
    getLoggedUser
};

export default connect(mapStateToProps, actionCreators)(hot(App));


You'll receive the error below if you forgot to import and configure redux-thunk to your project:

Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.


Here's an example configuration for redux-thunk:

import { applyMiddleware, compose, createStore, Store } from 'redux';

import { reducersRoot } from './reducers-root';

import { IAllState } from './all-state';

import ReduxThunk from 'redux-thunk';

export function configureStore(): Store<IAllState>
{
    const middlewares = applyMiddleware(ReduxThunk);

    const composeEnhancers = (window as any)['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] || compose;

    const composed = composeEnhancers(middlewares);

    return createStore(reducersRoot(), composed);
}

Another benefit of not using mapDispatchToProps is you can just pass the action creator directly to the action creators object, no need for the code to call the dispatch by itself. Making the code simple.

const actionCreators = {
    incrementCounter, 
    getLoggedUser
};

If needed be, it can be customized how an action creator is called:

const actionCreators = {
    incrementCounter: (n?: number) => incrementCounter(n! * 42),
    getLoggedUser
};

No comments:

Post a Comment