Next.js 14客户端组件useEffect页面加载时触发两次API请求问题
解决Next.js客户端组件useEffect调用API两次的问题
问题原因分析
- useEffect依赖触发重复执行:你的useEffect依赖数组包含
data,第一次请求完成后setData更新了data状态,会再次触发useEffect;此时inView仍为true,因此会发起第二次重复请求。 - 全局offset未更新:全局变量
offset始终为0,两次请求的参数完全一致,导致获取重复数据。 - useInView初始状态触发:组件挂载时
useInView可能直接返回inView=true,结合上述依赖问题,直接触发两次请求逻辑。
解决方案
1. 重构状态与useEffect逻辑
将全局offset改为React状态,使用setData的函数式更新避免依赖data,同时添加加载状态防止重复请求:
'use client'; import { useState, useEffect } from 'react'; import { useInView } from 'react-intersection-observer'; // ... 其他导入保持不变 export function ComboboxDemo({ services }: { services: string[] }) { const [open, setOpen] = useState(false); const [value, setValue] = useState(''); const searchParams = useSearchParams(); const initialQuery = searchParams.get('query_service') || ''; const [inputValue, setInputValue] = useState(initialQuery); const { replace } = useRouter(); const pathname = usePathname(); const { ref, inView } = useInView(); const [data, setData] = useState<string[]>([]); const [offset, setOffset] = useState(0); // 改为组件内部状态 const [isLoading, setIsLoading] = useState(false); // 加载锁 useEffect(() => { // 非可视区域或加载中时直接返回 if (!inView || isLoading) return; const fetchService = async () => { setIsLoading(true); try { const response = await fetch( `/dashboard/inventory/api/services?offset=${offset}`, ); const result = await response.json(); // 函数式更新获取最新状态,无需依赖data setData(prev => [...prev, ...result]); // 递增offset,对应API的limit=100 setOffset(prev => prev + 100); } catch (err) { console.error('请求失败:', err); } finally { setIsLoading(false); } }; fetchService(); }, [inView, offset, isLoading]); // 依赖数组移除data // ... 组件剩余代码 }
2. 优化API端点参数处理
确保offset转换为数字类型,避免潜在的类型问题:
import { db } from '@/drizzle/db'; import { evergreen } from '@/drizzle/schema'; import { sql } from 'drizzle-orm'; import { NextRequest, NextResponse } from 'next/server'; const limit = 100; export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url); // 将offset转换为数字,默认0 const offset = Number(searchParams.get('offset')) || 0; const dataPromise = db .selectDistinct({ it_service: evergreen.it_service, }) .from(evergreen) .offset(sql.placeholder('offset')) .limit(limit) .prepare('distinct_it'); const data = await dataPromise.execute({ offset }); console.log(data); const services = data.map((item) => item.it_service); return NextResponse.json(services, { status: 200 }); }
3. 可选:限制useInView触发次数
如果仅需要初始化加载一次数据,可给useInView添加triggerOnce: true配置,确保只触发一次请求:
const { ref, inView } = useInView({ triggerOnce: true });
内容的提问来源于stack exchange,提问作者moth
相关产品推荐
相关产品推荐

