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

Redux Toolkit异步thunk触发Actions must be plain objects报错

问题根因与修复方案

你遇到的两个报错和input输入框本身没有关系,是代码逻辑错误导致的,具体问题和修复方式如下:


1. 触发Actions must be plain objects的核心原因:thunk传参格式错误+代码顺序问题

你定义fetchSearch这个异步action时,明确要求入参是包含page(数字类型)和query(字符串类型)两个字段的对象:

export const fetchSearch = createAsyncThunk('search/fetchSearch', async ({page, query} : {page: number, query: string}) => {
  // 内部请求逻辑
});

但你在useEffect里dispatch时,直接传入了字符串类型的query,不符合入参约定;同时你当前代码是先定义slice、后定义fetchSearch,会触发JS暂时性死区风险,slice中引用的fetchSearch可能为undefined,最终导致thunk执行链路异常,抛出action格式错误。

// 错误写法
dispatch(fetchSearch(query));

修复方式

  • dispatch时传入符合类型要求的对象
  • 调整模块内代码顺序,先定义createAsyncThunk,再定义slice,避免引用undefined
// 正确写法:传入包含page和query的对象
dispatch(fetchSearch({ page: 1, query: query }));

2. 触发404错误的原因:API地址配置与请求拼接错误

TMDB V3版本API的合法基础地址必须携带/3后缀,如果你配置的REACT_APP_API_BASE值为https://api.themoviedb.org(漏了末尾的/3),最终拼接的请求地址不存在,会直接返回404。
另外你当前代码把page参数硬编码为1,没有使用入参的page值,且query参数没有做URL编码,搜索关键词包含空格、中文、特殊符号时会触发请求格式错误。

修复方式

  • 检查.env文件里的REACT_APP_API_BASE配置,确保值为https://api.themoviedb.org/3
  • 动态拼接page参数,用encodeURIComponent包裹query做编码:
const response = await axios.get<ISearch<IMovie>>(
  `${API_BASE}/search/movie?api_key=${TMDB_API_KEY}&query=${encodeURIComponent(query)}&page=${page}&include_adult=false`
);

额外必查项

如果改完上述逻辑还是报action格式错误,检查两个配置:

  • 确认searchSlice生成的reducer已经正确注册到configureStore中:
import { configureStore } from '@reduxjs/toolkit';
import searchReducer from './searchSlice实际路径';
export const store = configureStore({
  reducer: {
    search: searchReducer,
    // 其他业务reducer
  }
})
  • 确认组件内使用react-redux导出的useDispatch钩子获取dispatch方法,不要直接从redux包导入原生dispatch。
  • 给搜索input补上value={query}属性,符合React受控组件规范,避免输入值和state不同步。
  • 建议给输入加300ms左右的防抖,避免每输入一个字符就发一次请求,减少接口压力。

修复后参考代码

search模块完整代码

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import type { ISearch, IMovie, FetchStatus } from './你的类型定义路径';

const API_BASE = process.env.REACT_APP_API_BASE;
const TMDB_API_KEY = process.env.REACT_APP_API_KEY;

// 异步action放在slice前定义,避免引用问题
export const fetchSearch = createAsyncThunk('search/fetchSearch', async ({page, query} : {page: number, query: string}) => {
  const response = await axios.get<ISearch<IMovie>>(
    `${API_BASE}/search/movie?api_key=${TMDB_API_KEY}&query=${encodeURIComponent(query)}&page=${page}&include_adult=false`
  );
  return response.data;
});

interface ISearchState {
  searchQuery: string;
  searchPage: number;
  searchResult: ISearch<IMovie> | null;
  fetchStatus: FetchStatus | null;
}

const initialState: ISearchState = {
  searchQuery: '',
  searchPage: 1,
  searchResult: null,
  fetchStatus: null,
};

const searchSlice = createSlice({
  name:'search',
  initialState,
  reducers: {
    setSearchQuery: (state, action) => {
      state.searchQuery = action.payload;
    },
    setSearchPage: (state, action) => {
      state.searchPage = action.payload;
    }
  },
  extraReducers(builder) {
    builder
      .addCase(fetchSearch.pending, (state) => {
        state.fetchStatus = FetchStatus.PENDING;
      })
      .addCase(fetchSearch.fulfilled, (state, action) => {
        state.fetchStatus = FetchStatus.SUCCESS;
        state.searchResult = action.payload;
      })
      .addCase(fetchSearch.rejected, (state) => {
        state.fetchStatus = FetchStatus.FAILURE;
      })
  }
})

export const { setSearchQuery, setSearchPage } = searchSlice.actions;
export default searchSlice.reducer;

搜索栏组件代码

import { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { fetchSearch } from './searchSlice实际路径';

const SearchBar = () => {
  const [query, setQuery] = useState("");
  const dispatch = useDispatch();

  useEffect(() =>{
    if(query.length > 0){
      dispatch(fetchSearch({ page: 1, query }));
    }
  }, [query, dispatch])

  return (
    <form onSubmit={e => e.preventDefault()}>
      <input 
        type="search" 
        placeholder="Search for a movie..." 
        autoFocus
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
    </form>
  )
}

export default SearchBar;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:57:21