You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

区分React Redux应用状态与请求返回的状态码

Great question! This is a common point of confusion when working with Redux and async API calls—let's break down how to cleanly separate your app's business state from API response status codes, and handle them properly in your React Redux setup.


1. Separate Business State from Request Metadata

First, you need to draw a clear line between:

  • Application/Business State: The core data your app uses to render UI (e.g., the list of created groups).
  • Request Metadata: Data about the API call itself—loading status, error messages, and yes, the API response status code.

Store these in distinct parts of your Redux state. For example, your initial state might look like this:

const initialState = {
  // Business state: actual data used by your app
  groups: [],
  // Request metadata: tracks the create group API call's status
  createGroupRequest: {
    loading: false,
    error: null,
    statusCode: null // Holds the API's HTTP status code (200, 400, 500, etc.)
  }
};
2. Handle Async Flows in Action Creators

Since you're making AJAX calls, use an async middleware like Redux Thunk (or Redux Saga, if you prefer more control) to dispatch actions at different stages of the request: pending, fulfilled, and rejected.

Here's a thunk-based action creator that captures the API status code:

import axios from 'axios';

export const createGroup = (groupData) => async (dispatch) => {
  // Dispatch pending action to set loading state
  dispatch({ type: 'CREATE_GROUP_PENDING' });

  try {
    const response = await axios.post('/api/groups', groupData);
    // On success, send both the new group data and the status code
    dispatch({
      type: 'CREATE_GROUP_FULFILLED',
      payload: {
        group: response.data,
        statusCode: response.status
      }
    });
  } catch (error) {
    // On failure, capture the error message and status code (if available)
    dispatch({
      type: 'CREATE_GROUP_REJECTED',
      payload: {
        error: error.message,
        statusCode: error.response?.status || null
      }
    });
  }
};
3. Update Reducer to Track Both States

Your reducer will handle each action type to update either the business state or the request metadata:

const groupReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'CREATE_GROUP_PENDING':
      return {
        ...state,
        createGroupRequest: {
          ...state.createGroupRequest,
          loading: true,
          error: null,
          statusCode: null
        }
      };
    case 'CREATE_GROUP_FULFILLED':
      return {
        ...state,
        // Update business state with the new group
        groups: [...state.groups, action.payload.group],
        // Update request metadata with success status code
        createGroupRequest: {
          ...state.createGroupRequest,
          loading: false,
          statusCode: action.payload.statusCode
        }
      };
    case 'CREATE_GROUP_REJECTED':
      return {
        ...state,
        // Update request metadata with error and status code
        createGroupRequest: {
          ...state.createGroupRequest,
          loading: false,
          error: action.payload.error,
          statusCode: action.payload.statusCode
        }
      };
    default:
      return state;
  }
};
4. Use the States in Your Component

In your React component, pull both the business state and request metadata from the store, then use them to render appropriate UI feedback:

import { useSelector, useDispatch } from 'react-redux';
import { createGroup } from './groupActions';

const CreateGroupForm = () => {
  const dispatch = useDispatch();
  const { groups, createGroupRequest } = useSelector(state => state.groups);
  const { loading, error, statusCode } = createGroupRequest;

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = { /* get data from form fields */ };
    dispatch(createGroup(formData));
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        {/* Your form inputs go here */}
        <button type="submit" disabled={loading}>
          {loading ? 'Creating Group...' : 'Create Group'}
        </button>
      </form>

      {/* Show targeted messages based on status code */}
      {error && (
        <div className="error-alert">
          {statusCode === 400 && 'Invalid group details—please check your input.'}
          {statusCode === 401 && 'You need to log in to create a group.'}
          {statusCode === 500 && 'Oops, something went wrong on our end. Try again later.'}
          {!statusCode && error} {/* Fallback for network errors */}
        </div>
      )}

      {/* Show success message when group is created */}
      {statusCode === 201 && (
        <div className="success-alert">Group created successfully!</div>
      )}

      {/* Render the list of groups (business state) */}
      <ul>
        {groups.map(group => (
          <li key={group.id}>{group.name}</li>
        ))}
      </ul>
    </div>
  );
};
5. Bonus: Simplify with Redux Toolkit

If you're using Redux Toolkit (which I highly recommend—it cuts down on boilerplate), you can use createAsyncThunk to auto-generate pending/fulfilled/rejected actions, and createSlice to handle the reducer logic more cleanly:

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

export const createGroup = createAsyncThunk(
  'groups/createGroup',
  async (groupData, { rejectWithValue }) => {
    try {
      const response = await axios.post('/api/groups', groupData);
      return { group: response.data, statusCode: response.status };
    } catch (error) {
      return rejectWithValue({
        error: error.message,
        statusCode: error.response?.status || null
      });
    }
  }
);

const groupSlice = createSlice({
  name: 'groups',
  initialState: {
    groups: [],
    createGroupRequest: { loading: false, error: null, statusCode: null }
  },
  extraReducers: (builder) => {
    builder
      .addCase(createGroup.pending, (state) => {
        state.createGroupRequest.loading = true;
        state.createGroupRequest.error = null;
        state.createGroupRequest.statusCode = null;
      })
      .addCase(createGroup.fulfilled, (state, action) => {
        state.createGroupRequest.loading = false;
        state.createGroupRequest.statusCode = action.payload.statusCode;
        state.groups.push(action.payload.group);
      })
      .addCase(createGroup.rejected, (state, action) => {
        state.createGroupRequest.loading = false;
        state.createGroupRequest.error = action.payload.error;
        state.createGroupRequest.statusCode = action.payload.statusCode;
      });
  }
});

export default groupSlice.reducer;

The key takeaway is to keep your business state focused on what your app needs to render, and store API-specific metadata (like status codes) separately in request-tracked state. This keeps your Redux store organized and makes it easy to handle different user feedback scenarios based on API responses.

内容的提问来源于stack exchange,提问作者Arian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 06:22:52