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

RTK Query选择器返回undefined但状态存在数据的问题排查

问题:Redux Toolkit Query选择器无法获取预约数据

通过GET请求http://localhost:5000/shops/id/appointments获取指定店铺预约信息,Redux DevTools显示请求成功且状态中存在数据,但组件使用selectAppointmentsById选择器始终返回undefined。


前端请求实现

在AppointmentsList组件中通过路由参数id调用查询钩子:

const AppointmentsList = ()=>{
    const {id} = useParams();
    const {
        data:appointments,
        isLoading,
        isSuccess,
        isError,
        error
    } = useGetAppointmentsQuery(id);

后端响应数据

后端返回的JSON格式预约数据:

[
    {
        "active": true,
        "customerName": "Takis Makis",
        "service": "Kourema",
        "date": "12/12/2022",
        "_id": "63923aac73a0d02225738e69"
    },
    {
        "active": true,
        "customerName": "Mixalis Mixaliotis",
        "service": "Lousimo",
        "date": "01/-1/2023",
        "_id": "63923ac973a0d02225738e6d"
    },
    {
        "active": true,
        "customerName": "Ilias Tontoktoris",
        "service": "Xirisma",
        "date": "10/1/2023",
        "_id": "63923ae173a0d02225738e72"
    }
]

相关代码实现

appointmentsSlice代码

import { createSelector,createEntityAdapter } from "@reduxjs/toolkit";
import { apiSlice } from "../../app/api/apiSlice";
const appointmentsAdapter = createEntityAdapter({});

const initialState = appointmentsAdapter.getInitialState();

export const appointmentsApiSlice = apiSlice.injectEndpoints({ 
    endpoints:builder=>({
        getAppointments:builder.query({
            query:(id)=>`/shops/${id}/appointments`,                                       
            validateStatus:(response,result)=>{                       
                return response.status === 200 && !result.isError
            },
            keepUnusedDataFor:60,
            transformResponse:responseData=>{                                                               
                const loadedAppointments = responseData.map(appointment =>{           
                    appointment.id = appointment._id
                    return appointment
                });
                return appointmentsAdapter.setAll(initialState,loadedAppointments)   
            },
            providesTags:(result,error,arg)=>{                         
                if(result?.ids)
                {                                                      
                    return [
                        {
                            type:'Appointment', id:'LIST'
                        },
                        ...result.ids.map(id=>({type:'Appointment',id}))
                    ]
                }
                else return [{type:'Appointment',id:'LIST'}]
            }
        }),

apiSlice代码

import {createApi,fetchBaseQuery} from "@reduxjs/toolkit/query/react";
export const apiSlice = createApi({
    baseQuery:fetchBaseQuery({baseUrl:'http://localhost:5000'}),
    tagTypes:['Shop','User','Appointment'],
    endpoints:builder =>({})
});

Store配置

import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
import {apiSlice} from './api/apiSlice';
import {setupListeners} from '@reduxjs/toolkit/dist/query';
export const store = configureStore({
    reducer:{
        [apiSlice.reducerPath]:apiSlice.reducer,
    },
    middleware:getDefaultMiddleware=>
    getDefaultMiddleware().concat(apiSlice.middleware),
    devTools:true
});

setupListeners(store.dispatch);

前端路由配置

<Routes>
      <Route path='/' element={<Layout/>}>
        <Route index element={<Public/>}/>
        <Route path='/login' element={<Login/>}/>
        <Route path='/shops/:id/appointment'></Route> {/* 公开路由,用于创建预约 */}

     
        <Route path='dash' element={<DashLayout/>}>
          <Route index element={<Welcome/>}/>
          <Route path='shops'>
            <Route index element={<ShopsList/>}/> {/* 管理员可查看所有店铺,店主仅能查看自己的店铺*/}
            <Route path=':id'element={<AppointmentsList/>}/>
          </Route> 
        </Route>
       
      </Route>
    </Routes>

Appointment组件代码

const Appointment = ({appId})=>{
    const appointment = useSelector(state=>selectAppointmentsById(state,appId));
    console.log("test:",appointment)

    const navigate= useNavigate();
 
  

    if(appointment){
        const customerName = appointment.customerName;
        const service = appointment.service;
        const date = appointment.date;
      
        const handleEdit =()=>navigate(`/dash/appointments/edit/${appointment._id}`);
        
            return(
                <tr>
                    <td>{customerName}</td>
                    <td>{service}</td>
                    <td>{date}</td>
                    <td><button onClick={handleEdit}>Επεξεργασία</button></td>
                </tr>
            )
    }
    else
    return null
}

export default Appointment;

完整appointmentsSlice补充代码

const appointmentsAdapter = createEntityAdapter({});

const initialState = appointmentsAdapter.getInitialState();

export const appointmentsApiSlice = apiSlice.injectEndpoints({ 
    endpoints:builder=>({
        getAppointments:builder.query({
            query:(id)=>`/shops/${id}/appointments`,                                       
            validateStatus:(response,result)=>{                       
                return response.status === 200 && !result.isError
            },
            keepUnusedDataFor:60,
            transformResponse:responseData=>{                                                               
                const loadedAppointments = responseData.map(appointment =>{           
                    appointment.id = appointment._id
                    return appointment
                });
                return appointmentsAdapter.setAll(initialState,loadedAppointments)   
            },
            providesTags:(result,error,arg)=>{                         
                if(result?.ids)
                {                                                      
                    return [
                        {
                            type:'Appointment', id:'LIST'
                        },
                        ...result.ids.map(id=>({type:'Appointment',id}))
                    ]
                }
                else return [{type:'Appointment',id:'LIST'}]
            }
        }),
export const {
    useGetAppointmentsQuery,
} = appointmentsApiSlice

export const selectAppointmentsResult = appointmentsApiSlice.endpoints.getAppointments.select();

const selectAppointmentsData = createSelector(
    selectAppointmentsResult,
    appointmentsResult=>appointmentsResult.data 
)

export const {
    selectAll:selectAllAppointments,
    selectById:selectAppointmentsById,
    selectIds:selectAppointmentsIds,
} = appointmentsAdapter.getSelectors(state=>selectAppointmentsData(state)??initialState)

问题分析与解决方案

核心问题

RTK Query会根据查询参数生成唯一缓存键,当前selectAppointmentsResult未传入店铺id参数,导致selector尝试获取无参查询的缓存,无法匹配到对应店铺的预约数据。

修复方案

方案一:修改Selector支持参数传递

  1. 更新selectAppointmentsResult,使其接受查询参数:
export const selectAppointmentsResult = (shopId) => appointmentsApiSlice.endpoints.getAppointments.select(shopId);
  1. 在Appointment组件中,先获取对应店铺的查询结果,再提取单个预约:
import { useParams } from 'react-router-dom';
import { selectAppointmentsResult, selectAppointmentsById } from './appointmentsSlice';

const Appointment = ({appId})=>{
    const {id: shopId} = useParams();
    const appointmentsResult = useSelector(selectAppointmentsResult(shopId));
    const appointment = selectAppointmentsById(appointmentsResult.data ?? initialState, appId);

    console.log("test:", appointment);
    // 其余代码不变
}

方案二:直接传递数据给子组件

在AppointmentsList组件中直接将预约数据传递给Appointment,避免子组件使用selector查询:

// AppointmentsList组件中
if(isSuccess){
  return (
    <table>
      <tbody>
        {selectAppointmentsIds(appointments).map(appId => (
          <Appointment 
            key={appId} 
            appointment={selectAppointmentsById(appointments, appId)} 
          />
        ))}
      </tbody>
    </table>
  )
}

// Appointment组件直接使用props数据
const Appointment = ({appointment})=>{
    if(!appointment) return null;
    // 直接使用appointment数据渲染
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 13:40:27