Is using getState in a Redux Thunk good practice?

spunge picture spunge · Apr 6, 2017 · Viewed 20.9k times · Source

I have seen conflicting (or just confusing, to me) answers in other questions here regarding whether using getState within an action is acceptable, or not, and I have seen quite a few times it being called an anti-pattern. For me, it seems to work great but what is the best practice for doing this if we are not to use getState?

I am using getState within a thunk to filter through an array of users that is currently connected to some mock data and being pulled into the state of the application.

Here is the code for my action:

export const accountLogInSuccess = user => ({
    type: types.ACCOUNT_LOG_IN_SUCCESS,
    user,
});

export const accountLogOutSuccess = () => ({
    type: types.ACCOUNT_LOG_OUT_SUCCESS,
});

export const accountCheckSuccess = () => ({
    type: types.ACCOUNT_CHECK_SUCCESS,
});

export const accountCheck = () => (
    (dispatch, getState) => {
        dispatch(ajaxCallBegin());
        return apiAccount.accountCheck().then((account) => {
            if (account) {
                const user = findByUID(getState().users, account.uid);
                dispatch(accountLogInSuccess(user));
                toastr.success(`Welcome ${user.nameFirst}!`);
            } else {
                dispatch(accountLogOutSuccess());
            }
            dispatch(accountCheckSuccess());
        }).catch((error) => {
            dispatch(ajaxCallError(error));
            toastr.error(error.message);
            throw (error);
        });
    }
);

And my reducer:

export default function reducerAccount(state = initial.account, action) {
    switch (action.type) {
    case types.ACCOUNT_LOG_IN_SUCCESS:
        return Object.assign({}, state, action.user, {
            authenticated: true,
        });
    case types.ACCOUNT_LOG_OUT_SUCCESS:
        return Object.assign({}, {
            authenticated: false,
        });
    case types.ACCOUNT_CHECK_SUCCESS:
        return Object.assign({}, state, {
            initialized: true,
        });
    default:
        return state;
    }
}

The initial state of account used in my reducer is just:

account: {
    initialized: false,
    authenticated: false,
},

The accountCheck action passes the user (found using getState and the findByUID function) into accountLogInSuccess where the reducer adds its values to the current state of account via Object.assign.

Preferring to not have to get the user at the root of my application and then passing it down via props, what is the best practice to accomplish this within Redux and having the user data be available in state? Again, using getState within the thunk works great for me thus far, but is there a better solution to this that is not considered an anti-pattern?

Answer

markerikson picture markerikson · Apr 6, 2017

I wrote an extended blog post called Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability, which addresses this topic in detail. In it, I respond to several critiques of thunks and use of getState (including Dan Abramov's comments in Accessing Redux state in an action creator?). In fact, my post was specifically inspired by questions like yours.

As a TL;DR of my post: I believe that thunks are a completely viable tool for use in Redux applications, and encourage their use. While there are some valid concerns to be aware of when using thunks and sagas, and using getState/select inside of them, these concerns should not scare you away from using thunks.