React Query useInfiniteQuery类型不匹配导致npm run build失败
解决React Query无限滚动的类型不匹配问题
问题概述
用React Query结合react-intersection-observer实现无限滚动时,开发环境代码运行正常,但执行npm run build触发TypeScript类型不匹配错误。核心矛盾是getNextPageParam返回字符串类型的文档ID,而getInfinitePosts的pageParam被定义为number类型;尝试将pageParam改为字符串类型后,类型错误消失,但无限滚动功能失效。
错误提示
No overload matches this call. No overload matches this call. Overload 1 of 3, '(options: UndefinedInitialDataInfiniteOptions<DocumentList<Document> | undefined, Error, InfiniteData<DocumentList<Document> | undefined, unknown>, QUERY_KEYS[], number>, queryClient?: QueryClient | undefined): UseInfiniteQueryResult<...>', gave the following error. Type '(lastPage: DocumentList<Document> | undefined) => string | null' is not assignable to type 'GetNextPageParamFunction<number, DocumentList<Document> | undefined>'. Type 'string | null' is not assignable to type 'number | null | undefined'. Type 'string' is not assignable to type 'number'. Overload 2 of 3, '(options: DefinedInitialDataInfiniteOptions<DocumentList<Document> | undefined, Error, InfiniteData<DocumentList<Document> | undefined, unknown>, QUERY_KEYS[], number>, queryClient?: QueryClient | undefined): DefinedUseInfiniteQueryResult<...>', gave the following error. Type '(lastPage: DocumentList<Document> | undefined) => string | null' is not assignable to type 'GetNextPageParamFunction<number, DocumentList<Document> | undefined>'. Overload 3 of 3, '(options: UseInfiniteQueryOptions<DocumentList<Document> | undefined, Error, InfiniteData<DocumentList<Document> | undefined, unknown>, DocumentList<...> | undefined, QUERY_KEYS[], number>, queryClient?: QueryClient | undefined): UseInfiniteQueryResult<...>', gave the following error. Type '(lastPage: DocumentList<Document> | undefined) => string | null' is not assignable to type 'GetNextPageParamFunction<number, DocumentList<Document> | undefined>'.ts(2769) queryClient-b7fce2ff.d.ts(335, 5): The expected type comes from property 'getNextPageParam' which is declared here on type 'UndefinedInitialDataInfiniteOptions<DocumentList<Document> | undefined, Error, InfiniteData<DocumentList<Document> | undefined, unknown>, QUERY_KEYS[], number>' queryClient-b7fce2ff.d.ts(335, 5): The expected type comes from property 'getNextPageParam' which is declared here on type 'DefinedInitialDataInfiniteOptions<DocumentList<Document> | undefined, Error, InfiniteData<DocumentList<Document> | undefined, unknown>, QUERY_KEYS[], number>' queryClient-b7fce2ff.d.ts(335, 5): The expected type comes from property 'getNextPageParam' which is declared here on type 'UseInfiniteQueryOptions<DocumentList<Document> | undefined, Error, InfiniteData<DocumentList<Document> | undefined, unknown>, DocumentList<...> | undefined, QUERY_KEYS[], number>' (property) getNextPageParam: GetNextPageParamFunction<number, Models.DocumentList<Models.Document> | undefined> This function can be set to automatically get the next cursor for infinite queries. The result will also be used to determine the value of hasNextPage.
原代码片段
自定义Hook:useGetPosts
export const useGetPosts = () => { return useInfiniteQuery({ queryKey: [QUERY_KEYS.GET_INFINITE_POSTS], queryFn: getInfinitePosts, getNextPageParam: (lastPage) => { if (lastPage && lastPage.documents.length === 0) { return null; } const lastId = lastPage.documents[lastPage.documents.length - 1].$id; return lastId; }, }); };
数据请求函数:getInfinitePosts
export async function getInfinitePosts({ pageParam }: { pageParam: number }) { const queries = [Query.orderDesc('$updatedAt'), Query.limit(12)]; if (pageParam !== undefined) { queries.push(Query.cursorAfter(pageParam.toString())); } try { const posts = await databases.listDocuments( appwriteConfig.databaseId, appwriteConfig.postsCollectionId, queries ); if (!posts) throw Error; return posts; } catch (error) { console.log(error); } }
组件调用代码
const { ref, inView } = useInView(); const { data: posts, fetchNextPage, hasNextPage } = useGetPosts(); const [searchValue, setSearchValue] = useState(''); const debouncedSearch = useDebounce(searchValue, 500); const { data: searchedPosts, isFetching: isSearchFetching } = useSearchPosts(debouncedSearch); useEffect(() => { if (inView && !searchValue) { fetchNextPage(); } }, [inView, searchValue]);
解决方案
1. 统一参数类型为字符串
将getInfinitePosts的pageParam类型改为可选字符串,同时去掉不必要的类型转换:
export async function getInfinitePosts({ pageParam }: { pageParam?: string }) { const queries = [Query.orderDesc('$updatedAt'), Query.limit(12)]; // 直接使用pageParam,无需转换 if (pageParam) { queries.push(Query.cursorAfter(pageParam)); } try { const posts = await databases.listDocuments( appwriteConfig.databaseId, appwriteConfig.postsCollectionId, queries ); if (!posts) throw new Error('Failed to fetch posts'); return posts; } catch (error) { console.error('Error fetching posts:', error); // 必须抛出错误,让React Query捕获加载失败状态 throw error; } }
2. 显式指定React Query泛型类型
给useInfiniteQuery指定完整泛型参数,确保TypeScript正确推断pageParam的类型为字符串:
import { UseInfiniteQueryOptions, useInfiniteQuery } from '@tanstack/react-query'; // 替换为你项目中的实际类型导入 import { DocumentList, Document } from './models'; export const useGetPosts = () => { return useInfiniteQuery<DocumentList<Document>, Error, DocumentList<Document>, typeof QUERY_KEYS[], string>({ queryKey: [QUERY_KEYS.GET_INFINITE_POSTS], queryFn: getInfinitePosts, getNextPageParam: (lastPage) => { if (!lastPage || lastPage.documents.length === 0) { return null; } const lastId = lastPage.documents[lastPage.documents.length - 1].$id; return lastId; }, // 显式设置初始参数为undefined,符合string | undefined类型 initialPageParam: undefined, }); };
关键说明
之前修改字符串类型后无限滚动失效,大概率是两个原因:
- 原
getInfinitePosts中没有抛出错误,React Query无法感知请求失败,导致hasNextPage状态异常 - 未显式指定泛型,TypeScript自动推断错误,导致
pageParam传递异常
来源标注
内容的提问来源于stack exchange,提问作者Bogdan Titsky
相关产品推荐
相关产品推荐

