Redux-Toolkit Query首次请求报错,后续请求成功却无数据展示
问题描述
实现测试结果页面分页功能时遇到以下异常:
- 首次进入TestResultsList页面,
useGetTestResultsQuery发起的请求参数仅为'testResultsList',后端返回400错误(提示“Username not found”),请求result为undefined - 随后自动重新请求,控制台显示result包含有效数据(ids数组、entities对象、totalPages:2、currentUser对象),参数也完整(
['testResultsList', {...}]),但页面无数据展示 - Redux DevTools中
testResultsList始终显示首次请求的“Username not found”错误,无有效数据 - 切换分页或触发
refetchOnFocus时,上述流程重复:控制台显示有效数据,但错误状态持续
相关代码
TestResultsList.jsx
const TestResultsList = () => { const { username } = useAuth(); const [selectedTestResultId, setSelectedTestResultId] = useState(null); const [isEditing, setIsEditing] = useState(false); const [search, setSearch] = useState(''); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 10; const { data: testResults, isLoading, isSuccess, isError, error, } = useGetTestResultsQuery( ['testResultsList', { page: currentPage, limit: itemsPerPage, username }], { pollingInterval: 60000, refetchOnFocus: true, refetchOnMountOrArgChange: true, } );
testResultsApiSlice.js
import { createEntityAdapter, createSelector } from '@reduxjs/toolkit'; import { apiSlice } from '../../app/api/apiSlice'; const testResultsAdapter = createEntityAdapter({}); const initialState = testResultsAdapter.getInitialState(); export const testResultsApiSlice = apiSlice.injectEndpoints({ endpoints: (builder) => ({ getTestResults: builder.query({ query: ([, { page, limit, username }]) => ({ url: `/testResults`, params: { page, limit, username }, validateStatus: (response, result) => { return response.status === 200 && !result.isError; }, }), transformResponse: (responseData) => { const loadedTestResults = responseData.testResults.map((testResult) => { testResult.id = testResult._id; return testResult; }); return { ...testResultsAdapter.setAll(initialState, loadedTestResults), totalPages: responseData.totalPages, currentUser: responseData.currentUser, }; }, providesTags: (result, error, arg) => { console.log(result); console.log(arg); if (result?.ids) { return [ { type: 'TestResult', id: 'LIST' }, ...result.ids.map((id) => ({ type: 'TestResult', id })), ]; } else return [{ type: 'TestResult', id: 'LIST' }]; }, }), addNewTestResult: builder.mutation({ query: (formData) => ({ url: '/testResults', method: 'POST', body: formData, }), invalidatesTags: [{ type: 'TestResult', id: 'LIST' }], }), updateTestResult: builder.mutation({ query: (formData) => ({ url: '/testResults', method: 'PATCH', body: formData, }), invalidatesTags: (result, error, arg) => [ { type: 'TestResult', id: arg.id }, ], }), deleteTestResult: builder.mutation({ query: ({ id }) => ({ url: '/testResults', method: 'DELETE', body: { id }, }), invalidatesTags: (result, error, arg) => [ { type: 'TestResult', id: arg.id }, ], }), }), }); export const { useGetTestResultsQuery, useAddNewTestResultMutation, useUpdateTestResultMutation, useDeleteTestResultMutation, } = testResultsApiSlice; export const selectTestResultsResult = testResultsApiSlice.endpoints.getTestResults.select(); const selectTestResultsData = createSelector( selectTestResultsResult, (testResultsResult) => testResultsResult.data ); export const { selectAll: selectAllTestResults, selectById: selectTestResultById, selectIds: selectTestResultIds, } = testResultsAdapter.getSelectors( (state) => selectTestResultsData(state) ?? initialState );
后端getAllTestResults控制器代码
const getAllTestResults = async (req,res) => { const { page, limit, username } = req.query; const skip = (page - 1) * limit; const currentUser = await User.findOne({ username }) .select('-password') .lean() .exec(); if (!currentUser) { return res.status(400).json({ message: 'Current user not found' }); } const totalTestResults = await TestResult.countDocuments({ user: currentUser._id }); const totalPages = Math.ceil(totalTestResults / limit); const testResults = await TestResult.find({ user: currentUser._id }) .skip(skip) .limit(limit) .lean(); res.status(200).json({ totalPages, testResults, currentUser }); }
问题分析与解决方案
核心问题1:首次请求参数缺失username
首次渲染时useAuth()返回的username可能未加载完成,导致请求携带undefined的username,甚至触发RTK Query参数解析异常,出现仅'testResultsList'的无效参数。
解决方法:
添加skip配置,确保username存在时才发起请求:
const { data: testResults, isLoading, isSuccess, isError, error, } = useGetTestResultsQuery( ['testResultsList', { page: currentPage, limit: itemsPerPage, username }], { pollingInterval: 60000, refetchOnFocus: true, refetchOnMountOrArgChange: true, skip: !username, // 无username时跳过请求 } );
核心问题2:错误状态未被正确覆盖
首次请求的错误被缓存后,后续成功请求的结果无法覆盖错误状态,导致组件始终读取错误缓存。
解决方法:
- 调整
validateStatus逻辑,避免错误的状态判断:
validateStatus: (response) => { // 仅将200视为成功响应 return response.status === 200; },
- 优化
refetchOnMountOrArgChange配置,确保参数变化时强制更新缓存:
{ pollingInterval: 60000, refetchOnFocus: true, refetchOnMountOrArgChange: { refetchOnMount: true, refetchOnArgChange: true, refetchOnReconnect: true, }, skip: !username, }
核心问题3:缓存键不统一
首次无效请求的缓存键(带undefined的username)与后续有效请求的缓存键不一致,导致组件读取错误的缓存条目。
解决方法:
通过skip配置避免发起无效请求,确保所有请求都携带有效username,让缓存键统一,后续成功请求会自动覆盖之前的错误缓存。
内容的提问来源于stack exchange,提问作者zxc123
相关产品推荐
相关产品推荐

