简体   繁体   中英

How to trap HTTP 4xx errors when using Redux Toolkit async REST API pattern?

I have successfully gotten my React / Redux app to retrieve data from a REST API back end. I'm using the createAsyncThunk feature of Redux Toolkit, which automatically sets up reducers that are called when the HTTP fetch promise resolves with success or failure.

For this particular endpoint, I'd like the Redux store to reflect an error whenever an HTTP 404 Not Found is encountered. Currently that is not happening. The component shown below always returns "Loaded successfully". How can I make it display "Error" instead?

I understand that fetch doesn't resolve with an error on HTTP 4xx errors , and that I need to check the response code myself and resolve it as a failure. What I don't understand is where or how to do that in the code below. I struggle with understanding async/await conceptually, am new to Redux Toolkit, and the code below is already tweaking my brain pretty hard. Help?

Here is my full code:

features/recipeList/recipeListApi.js

export default async function recipeListApi(localApiKey) {
  const response = await fetch('https://httpstat.us/404');
  const responseJson = await response.json();

  return responseJson;
}

features/recipeList/recipeListSlice.js

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

const sliceName = "recipeList";
const initialState = {
  loading: false,
  error: null,
  data: null
};

export const fetchRecipeList = createAsyncThunk("recipeList/fetchRecipeList", async (thunkAPI) => {
  const response = await recipeListApi();
  return JSON.stringify(response);
});

const recipeListSlice = createSlice({
  name: sliceName,
  initialState: initialState,
  extraReducers: {
    [fetchRecipeList.pending]: state => {
      if (!state.loading) {
        state.loading = true;
      }
    },
    [fetchRecipeList.fulfilled]: (state, action) => {
      if (state.loading) {
        state.loading = false;
        state.data = action.payload;
      }
    },
    [fetchRecipeList.rejected]: (state, action) => {
      if (state.loading) {
        state.loading = false;
        state.error = action.payload;
      }
    }
  }
});

export const recipeListReducer = recipeListSlice.reducer;

components/RecipeList.js

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchRecipeList } from '../features/recipeList/recipeListSlice';

export const RecipeList = () => {

    const recipeList = useSelector(state => state.recipeList);
    const dispatch = useDispatch();

    /* Equivalent to componentDidMount() */
    useEffect(() => {
        dispatch(fetchRecipeList());
    }, []);

    return <>

        {recipeList.loading && <h1>Loading</h1>}

        {!recipeList.loading && recipeList.error !== null && <h1>Error</h1>}

        {!recipeList.loading && recipeList.error === null && <h1>Loaded successfully</h1>}

    </>;
}

Check if the response had a state of ok - or whatever condition you want to check your response for - and return a rejected promise like so:

export default async function recipeListApi(localApiKey) {
  const response = await fetch('https://httpstat.us/404');

  if(!response.ok) {
    return Promise.reject();
  }

  return await response.json();
}

if your slice [rejected], the state.error should be receive from action.error

// features/recipeList/recipeListSlice.js
[fetchRecipeList.rejected]: (state, action) => {
      if (state.loading) {
        state.loading = false;
        state.error = action.error;
      }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM