React Query预取数据正常,重取/失效触发RangeError栈溢出
问题描述
首次渲染时,预取的数据可正常加载(react-query工具中可见);但尝试重新获取数据或使查询失效时,会触发RangeError: Maximum call stack size exceeded错误。所有同类自定义useFetch钩子均存在该问题。尝试将fetchFilters改为Server Action时,系统提示响应数据格式不符合要求。
相关代码
app/api/filters.ts
import type { TFilterData, TFilterDataResponse } from '@/lib/types' import { baseURL } from './constants' import { get } from './apiClient' export const fetchFilters = async (): Promise<TFilterData> => { try { const path = `${baseURL}/api/dashboard/ratings/filter-options` const response = await get<TFilterDataResponse>(path) return response?.data?.data ?? {} } catch (error) { console.error('Error fetching filters:', error) throw error } }
app/lib/hooks/useFilters.ts
'use client' import { fetchFilters } from '@/app/api/filtersApi' import { useQuery, keepPreviousData } from '@tanstack/react-query' export const useFilters = () => { const { data: filters, isLoading: isLoadingFilters, error: errorFilters, } = useQuery({ queryKey: ['filters'], queryFn: fetchFilters, placeholderData: keepPreviousData, }) if (errorFilters) { console.log( '%cGET FILTERS', 'background: red; color: white; padding: 2px;', errorFilters.message ) } return { filters, isLoadingFilters, errorFilters, } }
app/dashboard/DashboardClient.tsx
'use client' const { filters: fullListFilters, isLoadingFilters, errorFilters, } = useFilters()
app/dashboard/page.tsx
'use server' const queryClient = new QueryClient() await queryClient.prefetchQuery({ queryKey: ['filters'], queryFn: fetchFilters, }) const dehydrateState = dehydrate(queryClient) return ( <HydrationBoundary state={dehydrateState}> <DashboardClient /> </HydrationBoundary> )
解决方案
1. 排查keepPreviousData引发的循环
keepPreviousData可能在特定场景下触发无限渲染循环,尤其是当数据结构存在引用循环或查询配置重复触发请求时。先移除该配置测试:
// 修改useFilters.ts中的useQuery配置 export const useFilters = () => { const { data: filters, isLoading: isLoadingFilters, error: errorFilters, } = useQuery({ queryKey: ['filters'], queryFn: fetchFilters, // 暂时移除placeholderData: keepPreviousData }) // 其余代码不变 }
2. 修复Server Action数据格式问题
转为Server Action时,需确保返回数据符合RSC序列化规则:
- 排除函数、Symbol、循环引用等无法序列化的类型
- 在返回前手动校验序列化可行性:
// 改为Server Action版本(app/actions/filters.ts) 'use server' import type { TFilterData, TFilterDataResponse } from '@/lib/types' import { baseURL } from '@/app/api/constants' import { get } from '@/app/api/apiClient' export const fetchFilters = async (): Promise<TFilterData> => { try { const path = `${baseURL}/api/dashboard/ratings/filter-options` const response = await get<TFilterDataResponse>(path) const data = response?.data?.data ?? {} // 校验序列化,提前发现问题 JSON.stringify(data) return data } catch (error) { console.error('Error fetching filters:', error) throw error } }
3. 统一服务端与客户端Query Client配置
确保服务端预取和客户端使用的Query Client配置一致,避免缓存策略冲突:
// app/dashboard/page.tsx 'use server' import { QueryClient } from '@tanstack/react-query' // 统一创建Query Client的逻辑 const createQueryClient = () => new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, // 匹配客户端默认设置 }, }, }) const queryClient = createQueryClient() await queryClient.prefetchQuery({ queryKey: ['filters'], queryFn: fetchFilters, }) const dehydrateState = dehydrate(queryClient) return ( <HydrationBoundary state={dehydrateState}> <DashboardClient /> </HydrationBoundary> )
4. 检查数据中的循环引用
栈溢出最常见原因是数据存在循环引用,React Query递归处理时会触发无限递归。可添加检测逻辑:
// 在fetchFilters返回前检查 const data = response?.data?.data ?? {} // 使用circular-json库检测循环引用 import CircularJSON from 'circular-json' try { CircularJSON.stringify(data) } catch (e) { console.error('数据包含循环引用:', e) // 手动清理循环引用属性,比如delete data.someCircularProp } return data
内容的提问来源于stack exchange,提问作者Isaac
相关产品推荐
相关产品推荐

