如何在useInfiniteQuery回调中调用React自定义Hook useSearcher?
问题
我有一个用于拉取数据并处理其他逻辑的自定义Hook:
const search = useSearcher(...);
该Hook会发送API请求并按需更新搜索状态——当搜索查询为空字符串时清空搜索状态、取消请求、请求成功时设置搜索结果、设置加载状态、请求成功但无匹配结果的状态等。search对象包含完整的搜索状态:
{ searchResults, //T[] isLoading, //boolean isNoMatchingResult, //boolean searchInputValue, //string resetSearchResults,// ()=>void ... }
我尝试将其用于为useInfiniteQuery拉取数据,代码如下:
const { status, data, error, isFetching, isFetchingNextPage, fetchNextPage, hasNextPage } = useInfiniteQuery('projects', (ctx) => useSearcher(ctx.pageParam), { getNextPageParam: (_lastGroup, groups) => groups.length, });
此时出现报错:
React Hook "useSearcher" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function
我清楚Hook不能在回调中调用,只能直接在组件或自定义Hook中使用,但该如何解决这个问题?除了将useSearcher中的数据请求代码提取为独立方法外,还有其他方案吗?
解决方案
除了提取独立请求方法外,还有以下两种可行方案:
1. 封装适配useInfiniteQuery的自定义Hook
把useSearcher和useInfiniteQuery的逻辑整合到一个新的自定义Hook中,在顶层调用useSearcher,再复用它的请求能力配合分页逻辑:
const useInfiniteSearcher = (initialPageParam) => { // 在Hook顶层调用useSearcher,遵守Hook规则 const { searchResults, isLoading, resetSearchResults, ...restSearchState } = useSearcher(initialPageParam); // 用useInfiniteQuery处理分页逻辑 const infiniteQueryResult = useInfiniteQuery( 'infinite-search', async (ctx) => { // 假设useSearcher暴露了底层请求方法,传入分页参数拉取数据 const nextPageResults = await restSearchState.fetchData(ctx.pageParam); return nextPageResults; }, { getNextPageParam: (_lastGroup, groups) => groups.length, initialPageParam } ); // 合并返回状态,整合两个Hook的能力 return { ...infiniteQueryResult, ...restSearchState, isLoading: isLoading || infiniteQueryResult.isFetching, searchResults: infiniteQueryResult.data?.pages.flat() || searchResults }; };
使用时直接在组件顶层调用这个新Hook:
const { data, isFetchingNextPage, fetchNextPage, hasNextPage, isLoading } = useInfiniteSearcher(0);
2. 修改useSearcher支持动态分页参数
调整useSearcher的逻辑,允许外部传入分页参数并触发新请求,再配合useInfiniteQuery的分页状态驱动更新:
// 修改后的useSearcher,新增分页参数管理逻辑 const useSearcher = ({ initialQuery, initialPageParam }) => { const [pageParam, setPageParam] = useState(initialPageParam); const [searchResults, setSearchResults] = useState([]); const [isLoading, setIsLoading] = useState(false); const [hasNextPage, setHasNextPage] = useState(true); // ...原有其他状态 // 分页参数变化时触发追加请求 useEffect(() => { if (pageParam === initialPageParam) return; const fetchNextPage = async () => { setIsLoading(true); try { const nextResults = await api.fetchData({ query: searchInputValue, page: pageParam }); setSearchResults(prev => [...prev, ...nextResults]); setHasNextPage(nextResults.length > 0); } catch (err) { // 错误处理逻辑 } finally { setIsLoading(false); } }; fetchNextPage(); }, [pageParam, searchInputValue]); // 暴露更新分页参数的方法 const updatePageParam = (newPage) => setPageParam(newPage); return { searchResults, isLoading, hasNextPage, updatePageParam, // ...原有其他返回状态 }; };
然后在组件中配合使用两个Hook,用useInfiniteQuery管理分页流程:
const { searchResults, isLoading, updatePageParam, hasNextPage } = useSearcher({ initialQuery: '', initialPageParam: 0 }); // useInfiniteQuery仅负责分页状态管理,实际请求由useSearcher处理 const { fetchNextPage } = useInfiniteQuery( 'projects', async (ctx) => { updatePageParam(ctx.pageParam); // 返回当前页切片结果,供useInfiniteQuery维护分页结构 return searchResults.slice(ctx.pageParam * 10, (ctx.pageParam + 1) * 10); }, { getNextPageParam: (_lastGroup, groups) => groups.length, initialPageParam: 0, // 禁用useInfiniteQuery自带的自动请求,由useSearcher控制 enabled: false } );
注意事项
- 第一种方案适合
useSearcher已暴露底层请求方法的场景,职责分离清晰,不需要大幅修改原有代码。 - 第二种方案需要调整原
useSearcher的逻辑,适合希望保留原有状态管理、不想拆分请求代码的场景。
内容的提问来源于stack exchange,提问作者user2363676
相关产品推荐
相关产品推荐

