Reducer返回Pending Promise问题:请求后未收到目标数据对象
Hey there! Let's break down why your reducer is getting a pending Promise instead of the actual API response data, and how to fix it.
The Root Cause
Redux's default behavior expects action creators to return plain action objects immediately. When you return an axios request (which is a Promise) directly from your action creator, the reducer receives that Promise while it's still pending—this is why you see "Action received" logged first, before the "Request" (which fires when the Promise resolves).
Solution: Use Redux Thunk Middleware
Redux Thunk is the most common way to handle asynchronous actions in Redux. It lets your action creators return a function (instead of a plain object), so you can wait for the API request to finish before dispatching the action with actual data.
Step 1: Install Redux Thunk
First, add the package to your project:
npm install redux-thunk # or yarn add redux-thunk
Step 2: Configure Your Store to Use Thunk
Update your src/index.js to apply the middleware when creating your Redux store:
import React from 'react'; import ReactDOM from 'react-dom'; import { createStore, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; // Import your root reducer import App from './App'; // Create store with thunk middleware const store = createStore( rootReducer, applyMiddleware(thunk) ); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
Step 3: Rewrite Your Async Action Creator
Instead of returning a Promise directly, return a function that takes dispatch as an argument. Inside this function, run your axios request, then dispatch the action with the resolved data once the request completes:
// Example action creator (update with your actual API endpoint) export const fetchSearchResults = (searchTerm) => { return (dispatch) => { axios.get(`https://your-api-endpoint.com/search?q=${searchTerm}`) .then(response => { console.log("Request"); // Dispatch action with the actual data object dispatch({ type: 'FETCH_SEARCH_RESULTS', payload: response.data }); }) .catch(error => { // Optional: Dispatch an error action to handle failures dispatch({ type: 'FETCH_SEARCH_FAILED', payload: error.message }); }); }; };
Step 4: Update Your Reducer to Handle the Action
Make sure your reducer correctly processes the action with the data payload:
const initialState = { data: null, loading: false, error: null }; const searchReducer = (state = initialState, action) => { switch(action.type) { case 'FETCH_SEARCH_RESULTS': console.log("Action received"); return { ...state, data: action.payload, // This is now the actual data object loading: false, error: null }; case 'FETCH_SEARCH_FAILED': return { ...state, loading: false, error: action.payload }; // Optional: Add a loading state action if you want to track pending requests case 'FETCH_SEARCH_START': return { ...state, loading: true, error: null }; default: return state; } }; export default searchReducer;
Bonus: Use Redux Toolkit (Recommended for Modern Redux)
If you're starting fresh or want a cleaner setup, Redux Toolkit is the official recommended approach—it includes Redux Thunk out of the box, plus utilities to simplify action and reducer creation. With createAsyncThunk, your async logic becomes even more concise:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; export const fetchSearchResults = createAsyncThunk( 'search/fetchResults', async (searchTerm) => { const response = await axios.get(`https://your-api-endpoint.com/search?q=${searchTerm}`); console.log("Request"); return response.data; } ); const searchSlice = createSlice({ name: 'search', initialState: { data: null, loading: false, error: null }, extraReducers: (builder) => { builder .addCase(fetchSearchResults.pending, (state) => { state.loading = true; }) .addCase(fetchSearchResults.fulfilled, (state, action) => { console.log("Action received"); state.loading = false; state.data = action.payload; }) .addCase(fetchSearchResults.rejected, (state, action) => { state.loading = false; state.error = action.error.message; }); } }); export default searchSlice.reducer;
With this setup, your reducer will only receive the action once the API request is complete, giving you the actual data object instead of a pending Promise.
内容的提问来源于stack exchange,提问作者Lucas Peixoto

