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?
If you pass a function into dispatch , the thunk middleware sees that it's a function instead of an action object, intercepts it, and calls that function with (dispatch, getState) as its arguments. If it's a normal action object (or anything else), it's forwarded to the next middleware in the chain.
A very common pattern in Redux is to use things called a Thunks, which are a way of wrapping up certain logic of a subroutine in a single function.
But on the other hand, for bigger projects, Redux-Thunk may sometimes get you in trouble, as it can be hard to scale if your side effect or asynchronous logic increases, whereas in the case of Redux-Saga, it comes power-packed with some amazing things such as concurrent side effects, canceling side effects, debouncing ...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With