1
votes

I have many async Redux actions that all fetch different resources from an API. Now if the user is not authenticated all these API calls will return a 401.

Now I am looking for a way to catch all these and automatically navigate the user to the login page using history.push('/login'). Now I thought of a "smart" way of doing this by having all these async actions set a 401 status to the action object. Subsequently a reducer would catch all these actions by their status property being equal to 401. The user would then be navigated to the login page by a history.push() call inside this reducer:

import { history } from '../routers/AppRouter'

export default (state = {}, { status }) => {
    if (!status) return state
    switch (status) {
        case 401: {
            history.push('/login')
            break
        }
        default: {
        }
    }
    return state
}

However, as I already expected, but hoped to false, this is not possible, since a reducer is not allowed to have side-effects... I get the following error as a result:

Error: You may not unsubscribe from a store listener while the reducer is executing.

My question thus is, if there is a way to make this work inside the reducer? Or is there some other way of doing this. I'd really like to avoid having to put the following code in every async action:

if (response.status === 401) history.push('/login')
1
Hi did you find a solution to this problem ? I am also having a similar issue. How did you solve this? - fizmhd
@fizmhd I just added an answer! I hope it helps - Marnix.hoh

1 Answers

1
votes

So I did find a solution to this. Since it is a "workaround" I didn't bother posting it at first, but since others are apparently facing the same issue, I decided to share how I solved the problem anyway:

First of all, it is not possible (or at least highly discouraged) for a reducer to have any side-effects, such as calling history.push(). The workaround I used, was to create a custom error handler function, which looks pretty similar to a reducer:

export default (status, dispatch) => {
    switch (status) {
        case 401: {
            history.push('/login')
            break
        }
        case 404: {
            history.push('/404')
            break
        }
        default: {
            return
        }
    }
}

Inside of my async action (thunk) I then have something like this:

if (status === 200) {
    dispatch( <some success action> )
} else {
    dispatch( <some failure action> )
    errorHandler(status, dispatch)
}

This way, the "side-effect" happens inside of the thunk, which is allowed as opposed to inside the reducer (which is called by dispatch()).

I know it is slightly annoying to always have to include the errorHandler(status, dispatch) line, but at least all of the error handling code is obfuscated this way.

I hope this helps someone! If you have any other suggestions on how to do this, I'd love to hear them too! :)