Search code examples
javascriptnode.jsreactjsredux-thunkredux-toolkit

CreateAsyncThunk Problem with 400 Bad request


i have created a asyncthunk, but when status code is 400, the result of request is still fulfilled, which method is to handle a 400 error with createasyncthunk and fetch api?

This is a action code:

    import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import authService from "./auth-service";


// Get user from localStorage
const user = JSON.parse(localStorage.getItem("user"));

const initialState = {
  user: user ? user : "",
  isError: false,
  isSuccess: false,
  isLoading: false,
  message: "",
};

// Register user
export const register = createAsyncThunk(
  "auth/signup",
  async (user, thunkAPI) => {
    try {
      return await authService.register(user);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Login user
export const login = createAsyncThunk("auth/login", async (user, thunkAPI) => {
  try {
    return await authService.login(user);
  } catch (error) {
    const message =
      (error.response && error.response.data && error.response.data.message) ||
      error.message ||
      error.toString();
    return thunkAPI.rejectWithValue(message);
  }
});

export const logout = createAsyncThunk("auth/logout", async () => {
  await authService.logout();
});

export const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    reset: (state) => {
      state.isLoading = false;
      state.isSuccess = false;
      state.isError = false;
      state.message = "";
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(register.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(register.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.isSuccess = false;
        state.user = "";
      })
      .addCase(register.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isError = false;
        state.isSuccess = true;
        state.message = action.payload;
      })
      .addCase(login.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(login.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.user = action.payload;
      })
      .addCase(login.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
        state.user = "";
      })
      .addCase(logout.fulfilled, (state) => {
        state.user = "";
      });
  },
});

export const { reset } = authSlice.actions;
export default authSlice.reducer;

This is a service code:

import React from 'react'
import Cookies from 'js-cookie'
const API_URL = "http://localhost:5000/api/auth/";


const token = Cookies.get('XSRF-Token')

// Register user
const register = async (userData) => {
  const response = await fetch(API_URL + "signup", {
    method: "POST",
    credentials: 'include',
    headers: {
      'X-CSRF-Token': token,
      'Accept': "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify(userData),
  });

  const responseData = await response.json();

  if (response.ok) {
    localStorage.setItem("user", JSON.stringify(responseData));
    return responseData;
  }
};

// Login user
const login = async (userData) => {
  const response = await fetch(API_URL + "login", {
    method: "POST",
    credentials: 'include',
    body: JSON.stringify(userData),
    headers: {
      'X-CSRF-Token': token,
      'Accept': "application/json",
      "Content-Type": "application/json",
    },
  });

  const responseData = await response.json();

  if (response.ok) {
    localStorage.setItem("user", JSON.stringify(responseData));
    return responseData;
  }
};

// Logout user
const logout = () => {
  localStorage.removeItem("user");
};

const authService = {
  register,
  logout,
  login,
};

export default authService;

The problem is both register and login action, return a fulfilled even if the code of post request is 400 and my intention is send a message about error


Solution

  • Your code doesn't do anything if response.ok is not truthy. That's what will happen when you get a 4xx status.

    So, for example, in this code:

    // Register user
    const register = async (userData) => {
      const response = await fetch(API_URL + "signup", {
        method: "POST",
        credentials: 'include',
        headers: {
          'X-CSRF-Token': token,
          'Accept': "application/json",
          "Content-Type": "application/json",
        },
        // body: JSON.stringify(userData),
      });
    
      const responseData = await response.json();
    
      if (response.ok) {
        localStorage.setItem("user", JSON.stringify(responseData));
        return responseData;
      }
    };
    

    You just allow the async function to have an undefined return value if response.ok is not truthy. That will resolve the promise, with an undefined resolved value.

    Perhaps, you want to turn any non-2xx status into a rejection like this:

    // Register user
    const register = async (userData) => {
      const response = await fetch(API_URL + "signup", {
        method: "POST",
        credentials: 'include',
        headers: {
          'X-CSRF-Token': token,
          'Accept': "application/json",
          "Content-Type": "application/json",
        },
        // body: JSON.stringify(userData),
      });
    
      if (response.ok) {
        const responseData = await response.json();
        localStorage.setItem("user", JSON.stringify(responseData));
      } else {
        // reject the promise
        throw new Error(`status code ${response.status}`)
      }
    
      
    };
    

    You would probably want to do something similar for all your uses of fetch().

    Since I find that I want this to be the standard behavior when making requests, I have my own fetchWrapper() function that automatically converts any non-2xx status to a promise rejection so I don't have to code that into every use of fetch().


    Here's an example of a fetchWrapper function that turns any http status that is not in the range of 200-299 into a rejected promise:

    async function fetchJSON(url, options) {
        let response = await fetch(url, options);
        if (!response.ok) {
            throw new Error(`status code ${response.status}`);
        }
        return response.json();
    }