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

useState初始化后无法更新:带搜索框的无限滚动问题排查

问题:带搜索框的无限滚动页码异常

我正在实现带搜索框的无限滚动功能,每次调用API获取10条数据并将页码递增1。使用useState管理pageNumber,在子组件调用fetchMore函数前执行setPageNum,但pageNum始终停留在初始值1,最多只能到2,无法继续递增。尝试多种方法5小时仍未解决,求排查错误及修复方案。

父组件代码

const InputTodo = ({ setTodos }) => {
  const [inputText, setInputText] = useState("");
  const [suggestionList, setSuggestionList] = useState([]);
  const [params, setParams] = useState(null);
  const [pageNum, setPageNum] = useState(1);

  const debouncedText = useDebounce(inputText, 500);

  useEffect(() => {
    if (!debouncedText) {
      return;
    }
    async function fetchData() {
      const { data } = await getSearchSuggestion(debouncedText, 1);
      const { result, total, limit, page } = data;
      setPageNum(page);
      setSuggestionList(result || []);
      setParams({
        total,
        limit
      });
    }
    fetchData();
  }, [debouncedText]);

  const handleFetchMore = useCallback(
    async (query) => {
      if (params.total < params.limit * (pageNum - 1)) {
        return;
      }
      try {
        const { data } = await getSearchSuggestion(query, pageNum + 1);
        setSuggestionList((prev) => [...prev, ...data.result]);
        setPageNum(data.page);
      } catch (error) {
        console.log(error);
      }
    },
    [pageNum, params]
  );

子组件代码

//child component
const InputDropdown = ({
  query,
  suggestions,
  pageNum,
  setPageNum,
  limit,
  total,
  onFetchMore
}) => {
  const [isBottom, setIsBottom] = useState(null);
  const bottomObserver = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          if (total < limit * pageNum) {
            return;
          }
          setPageNum((prev) => prev + 1);
          onFetchMore(query);
        }
      },
      { threshold: 0.25 }
    );
    bottomObserver.current = observer;
  }, []);

错误原因分析

  1. 子组件闭包陷阱:子组件中useEffect的依赖数组为空,导致IntersectionObserver的回调函数捕获的pageNum、total、limit等参数始终是组件初始化时的初始值,后续状态更新后回调无法拿到最新值,判断逻辑失效。
  2. 异步状态更新时序问题:子组件中先调用setPageNum再执行onFetchMore,但setPageNum是异步操作,此时onFetchMore(即父组件的handleFetchMore)拿到的pageNum还是更新前的旧值,导致请求的页码始终是旧值+1,无法正确递增。
  3. 父组件回调依赖问题:虽然handleFetchMore用了useCallback,但依赖的pageNum是快照值,若子组件传递的时机不对,可能拿到的不是最新状态。

修复方案

步骤1:修复子组件的IntersectionObserver依赖

更新子组件的useEffect依赖数组,确保回调能获取最新状态,同时移除提前调用setPageNum的操作,改为由父组件处理页码递增:

const InputDropdown = ({
  query,
  suggestions,
  pageNum,
  limit,
  total,
  onFetchMore
}) => {
  const bottomObserver = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          // 计算已加载数量:limit * pageNum,若总数小于等于已加载则停止
          if (total <= limit * pageNum) {
            return;
          }
          // 直接调用onFetchMore,由父组件处理页码
          onFetchMore(query);
        }
      },
      { threshold: 0.25 }
    );

    // 补充挂载观察目标的逻辑(需确保页面存在对应id的元素)
    const bottomElement = document.getElementById('dropdown-bottom');
    if (bottomElement) {
      observer.observe(bottomElement);
    }

    // 清理函数:避免内存泄漏
    return () => {
      if (bottomElement) {
        observer.unobserve(bottomElement);
      }
      observer.disconnect();
    };
  }, [pageNum, limit, total, query, onFetchMore]); // 添加所有依赖项

步骤2:优化父组件的handleFetchMore逻辑

基于当前页码计算下一页,确保每次请求的是正确的页码:

const handleFetchMore = useCallback(
  async (query) => {
    if (!params) return; // 避免params为null时报错
    const { total, limit } = params;
    // 计算已加载数量,若总数小于等于已加载则停止请求
    if (total <= limit * pageNum) {
      return;
    }
    try {
      const nextPage = pageNum + 1;
      const { data } = await getSearchSuggestion(query, nextPage);
      setSuggestionList((prev) => [...prev, ...(data.result || [])]);
      setPageNum(nextPage); // 直接用计算后的页码更新
    } catch (error) {
      console.log(error);
    }
  },
  [pageNum, params]
);

步骤3:确保搜索关键词变化时的状态重置

原代码中搜索关键词变化时已重置pageNum为1并清空建议列表,这部分逻辑正确,需保留以保证新搜索从第一页开始。

额外注意事项

  • 需确保子组件中存在被IntersectionObserver观察的底部元素(如添加id为dropdown-bottom的空div在列表末尾),否则回调不会触发。
  • 可添加加载状态变量,避免滚动到底部时重复触发API请求。

内容的提问来源于stack exchange,提问作者Ava Kim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 04:36:22