Handling errors with redux-toolkit
Asked Answered
F

4

29

The information about the error in my case sits deeply in the response, and I'm trying to move my project to redux-toolkit. This is how it used to be:

catch(e) {
  let warning
  switch (e.response.data.error.message) { 
    ...
  }
}

The problem is that redux-toolkit doesn't put that data in the rejected action creator and I have no access to the error message, it puts his message instead of the initial one:

enter image description here

While the original response looks like this:

enter image description here

So how can I retrieve that data?

Fornicate answered 16/8, 2020 at 16:1 Comment(0)
B
48

Per the docs, RTK's createAsyncThunk has default handling for errors - it dispatches a serialized version of the Error instance as action.error.

If you need to customize what goes into the rejected action, it's up to you to catch the initial error yourself, and use rejectWithValue() to decide what goes into the action:

const updateUser = createAsyncThunk(
  'users/update',
  async (userData, { rejectWithValue }) => {
    const { id, ...fields } = userData
    try {
      const response = await userAPI.updateById(id, fields)
      return response.data.user
    } catch (err) {
      if (!err.response) {
        throw err
      }

      return rejectWithValue(err.response.data)
    }
  }
)
Bayard answered 16/8, 2020 at 19:40 Comment(9)
in case you want to make a request without passing in any data, you would have to do something like async ( _ , { rejectWithValue }) => { ....Inductee
@Bayard Can you please take a look at the following question:#66435776Lordly
That's very odd default behaviour, rather than returning the error as is - but yeah, that is as documented. Annoying to have to add boilerplate try { return await thing() } catch (error) { return rejectWithValue(error) } instead of just return thing().Discontented
Not sure what you're suggesting there as an alternative. createAsyncThunk will handle basic thrown errors just fine by default. The example I showed is only if you want your /rejected action to contain some value that was in the response, instead of a serialized version of the error itself.Bayard
It can also be done using a response interceptorHoagy
I think this is bad too. In my case, basically all my calls will need this because I can return custom errors from the server and want to display the appropriate error message, that may also execute some code or custom behaviour, and in unexpected errors show some default error message to the user. The fact that javascript (as opposed to other languages) only allow the error object to have a message field is by itself already bad, but this behavior of the redux toolkit made things worse :/ For now I created a custom function that does this logic, but still makes the call a bit polluted.Twoedged
The docs say that "everything that does not match the SerializedError interface will have been removed from it". How do you setup a response from the server to match it? I've tried to send back- body: { message: "Unique message here" } but didn't get any luck..Vorous
@Bayard Thank you a ton! For anyone who is looking for TypeScript version there is a good explanation. redux-toolkit.js.org/usage/…Thun
any idea why error is serialized in the first place?Mannos
G
7

We use thunkAPI, the second argument in the payloadCreator; containing all of the parameters that are normally passed to a Redux thunk function, as well as additional options: For our example async(obj, {dispatch, getState, rejectWithValue, fulfillWithValue}) is our payloadCreator with the required arguments;

This is an example using fetch api

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

export const getExampleThunk = createAsyncThunk(
    'auth/getExampleThunk',
    async(obj, {dispatch, getState, rejectWithValue, fulfillWithValue}) => {
        try{
            const response = await fetch('https://reqrefs.in/api/users/yu');
            if (!response.ok) {
                return rejectWithValue(response.status)
            }
            const data = await response.json();
            return fulfillWithValue(data)
        }catch(error){
            throw rejectWithValue(error.message)
        }
    }
)   

Simple example in slice:

const exampleSlice = createSlice({
    name: 'example',
    initialState: {
        httpErr: false,
    },
    reducers: {
        //set your reducers
    },
    extraReducers: {
        [getExampleThunk.pending]: (state, action) => {
            //some action here
        },
        [getExampleThunk.fulfilled]: (state, action) => {
            state.httpErr = action.payload;
        },
        [getExampleThunk.rejected]: (state, action) => {
            state.httpErr = action.payload;
        }
    }
})

Handling Error

Take note: rejectWithValue - utility (additional option from thunkAPI) that you can return/throw in your action creator to return a rejected response with a defined payload and meta. It will pass whatever value you give it and return it in the payload of the rejected action.

Giblet answered 20/7, 2021 at 14:34 Comment(0)
P
0

For those that use apisauce (wrapper that uses axios with standardized errors + request/response transforms)

Since apisauce always resolves Promises, you can check !response.ok and handle it with rejectWithValue. (Notice the ! since we want to check if the request is not ok)

export const login = createAsyncThunk(
  "auth/login",
  async (credentials, { rejectWithValue }) => {
    const response = await authAPI.signin(credentials);
    if (!response.ok) {
      return rejectWithValue(response.data.message);
    }
    return response.data;
  }
);
Pearlinepearlman answered 5/5, 2021 at 21:34 Comment(0)
T
0

My problem was not being abole instantly detect error in the component, after await dispatch throws rejected. i tried try catch with the code where i dispatched, the rejectWithValue doesnot throw an exception.

I found a trick, i used this code from the thunkapi website documentation, it worked for me to detect if the thunkapi threw an exception or not.

    const submitLogin = async ()=>{
    try {
        const resultAction = await dispatch(doLogin({ username, password }));
        const originalPromiseResult = unwrapResult(resultAction)//is needed to throw error
        // console.log("original promise here")
        // console.log(originalPromiseResult)//you can read succees response here, but error goes to the catch
        
        // If login is successful, redirect to the home page
        //do something here like navigate to home
      } catch (rejectedValueOrSerializedError) {
        // Handle login error
        const errorResponse = rejectedValueOrSerializedError//this is the backend api error result
        //handle the error in the ui and do what you need to do
      }
}
Translatable answered 12/11, 2023 at 9:0 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.