redux-toolkit: RTK Query gets stuck pending and swallows errors thrown in addMatcher reducers

I just ran into this problem and found this comment saying it’s expected behavior, but I’m making this issue with a clearer example for further discussion because this seems like a footgun that should be changed.

Below is an example with a login button. Click the button and it sends a POST request to the backend with credentials to log in and gets back user info + token, and stores them in the authSlice and displays the username.

https://codesandbox.io/p/github/adamerose/rtkq-reducer-error-example/master https://github.com/adamerose/rtkq-reducer-error-example

With the error thrown in the matchFulfilled reducer, the query gets stuck forever in this state even though the HTTP request completed successfully:

isUninitialized:false
isFetching:true
isSuccess:false
isError:false
const authSlice = createSlice({
  name: "auth",
  initialState: initialState,
  reducers: {
    logout: (state) => {
      state.user = null
    },
  },
  extraReducers: (builder) => {
    builder.addMatcher(
      api.endpoints.login.matchFulfilled,
      (state, { payload }) => {
        state.user = payload.user

        throw Error("Example error")
      },
    )
  },
})

It also seems to completely swallow the error and nothing appears in the console, which made it very hard to figure out why I was getting stuck pending in an actual project. I’ve included a separate Throw Error button that throws an error in a normal reducer and I can see it appear in the console, but for some reason the one inside the addMatcher example doesn’t. Am I doing something wrong here?

image image image

About this issue

  • Original URL
  • State: open
  • Created 9 months ago
  • Reactions: 1
  • Comments: 21 (1 by maintainers)

Most upvoted comments

You’ve added extra reducer code, that runs in the same root reducer, handling the same action. So when the one call to rootReducer(state, rtkqFulfilledAction) errors, that prevents the RTKQ fulfillment update from being saved too.

Okay that makes sense, so it sounds like the only fix would be separate actions like I described. Or in my own code I can wrap all reducers that touch internal RTK actions with a try/catch that returns initial state on failure.

We aren’t going to rearchitect RTKQ’s internals just to try to work around the chance that a user might add extra reducers listening to the same action, and those reducers might someday throw an error.

That’s fair enough. Maybe reconsider it if you come across other problems caused by mixing internal and user actions, I agree this issue alone wouldn’t justify the effort. If the error swallowing gets fixed then this becomes a much smaller pain point.

Thanks for all your work, and quick response time!

Generally quite confused by this discussion.

At first glance, that sounds like normal Redux behavior.

If you look at the Redux core inside of dispatch(), it does:

try {
  isDispatching = true
  currentState = currentReducer(currentState, action)
} finally {
  isDispatching = false
}

So any error in a reducer gets swallowed.

I don’t see anywhere an error would be swallowed here? Errors thrown in a reducer will be uncaught as usual, as shown by the “Throw error” in the linked CSB. Though as covered, reducers should never throw unless something is very wrong.

Errors thrown in an addMatcher reducer shouldn’t be caught either: Error reducer playground

Whatever’s happening with regards to the error being swallowed seems very specific to RTKQ - either during its middleware, thunks, or hooks.