Question about error handling in redux toolkit (rejected, fulfilled)

I’m getting into using redux-toolkit and after reading the docs and implementing some code, there’s something I don’t quite understand, or maybe my approach is wrong, thats why I’m asking here.

Anyways, I’m having some slice with some asyncThunk which makes a call to an API and sets some state from values returned from that call, and if it fails, show an error notification and some logging. I’m not at work at the moment so I’ll write some pseudo code:

const loadData = createAsyncThunk('loadData',
    async ({ product }, { dispatch, extra: { API }, rejectWithValue }) => {
        try {
            const response = await API.getProduct(product)
            return response
        } catch (error) {
            dispatch(setErrorNotification(error.message))
            dispatch(logError(error))
            rejectWithValue(null)
        }
    }

This code does what I want actually, but my question lies in having to use rejectWithValue(null)for the code to go through the rejected reducer, , which looks really fishy to me and it makes me believe I’m not understanding this quite well. If I don’t do this, even if it goes into the catch, it goes through the fulfilled reducer which throws an error since this is not expected and there’s no payload.

Any pointers? is there a way for, when catching the API call (in case of failure) going to the rejected reducer without having to explicitly rejectWithValue?

Thanks!

Answer

It is not fishy at all. It is the way to put all burden of changing state to reducers in convenient way. Let’s look at code like this: let promise = promiseReturningFunction();. Here we have some generic promise and now we can “subscribe” on future value of it with then and catch. We also can subscribe to future values multiple times, like this:

let promise = promiseReturningFunction();
promise.then(doSomething);
promise.then(doSomethingElse);

Redux toolkit preserves this pattern. Thunks are detached from specific slices and you can “subscribe” to them in multiple reducers. So when you want to do multiple things in different slices on same result, you will get raw data from thunk and in different slices process that data as you need in particular case.

In your case, you should call return rejectWithValue(error). Then, in slice that is responsible for errors you add this:

extraReducers: {
  [loadData.rejected]: (state, action) => {
    // do something with error that is in action.payload
  }
}

This is good pattern in case when you handle errors in one slice, but might need to know that it settled in another. It is also future proof in two ways: you always can add slice that use same thunk, and you always can replace thunk completely without breaking things (if shapes of data don’t change).

Update

Example sandbox: https://codesandbox.io/s/fancy-cdn-d86c9?file=/src/App.js

Leave a Reply

Your email address will not be published. Required fields are marked *