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

Next.js 14客户端组件useEffect页面加载时触发两次API请求问题

解决Next.js客户端组件useEffect调用API两次的问题

问题原因分析

  1. useEffect依赖触发重复执行:你的useEffect依赖数组包含data,第一次请求完成后setData更新了data状态,会再次触发useEffect;此时inView仍为true,因此会发起第二次重复请求。
  2. 全局offset未更新:全局变量offset始终为0,两次请求的参数完全一致,导致获取重复数据。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 17:33:20