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

React-Query useQuery异步获取时跳过条目问题排查

类似Tinder应用的分页加载异常问题

问题背景

开发类似Tinder的应用,核心功能逻辑:

  • 每次从数据库获取4条用户条目
  • 用户逐个查看,点击点赞/点踩按钮触发异步函数将操作写入数据库
  • 滑到当前获取的最后一条条目时,自动获取下一批4条数据

组件代码

export const HomePage: React.FC = () => {
  const { user } = useUser()
  const userId = user?.id
  const [current, setCurrent] = useState<number | null>(0)
  const [skip, setSkip] = useState(0)

  const [url, setUrl] = useState(
    `/api/swipes/get-users/?userId=${user?.id}&skip=${skip}`
  )

  const getUsers = async (url: string) => {
    const { data } = await axios.get(url)
    return data
  }

  const { error, data, isLoading, refetch } = useQuery(
    ['userSwipeData', url],
    () => getUsers(url),
    {
      enabled: !!user?.id,
    }
  )

  const handleRefetchUsers = () => {
    setCurrent(0)
    setUrl(
      `/api/swipes/get-users/?userId=${user?.id}&skip=${
        skip + FETCH_USERS_PAGINATION_LIMIT
      }`
    )
    refetch()
    setSkip(skip + FETCH_USERS_PAGINATION_LIMIT)
  }

  const handleSwipe = async (e: any) => {
    if (current === null || !data) return

    const { value } = e.target
    await handleSwipeType(value, data?.users[current].id)
  }

  const handleSwipeType = async (type: string, id: string) => {
    const values = {
      userSwipeOn: id,
      currentUser: userId,
    }

    // if (type === 'YES') {
    //   if (current && data?.users[current]?.isMatch) {
    //     await axios.post('/api/swipes/create-match', values)
    //     alert('You have a match!')
    //   }

    //   await axios.post('/api/swipes/like', values)
    // } else {
    //   await axios.post('/api/swipes/dislike', values)
    // }
    if (current === data.users.length - 1) {
      handleRefetchUsers()
    } else {
      setCurrent(current! + 1)
    }
  }

  if (isLoading || current === null) return <Box>Fetching users...</Box>
  if (error) return <Box>An error has occurred </Box>
  if (data && !data?.users.length) return <Box>No more users for now</Box>

  return (
    <Box>
      <h1>Current User: {data?.users[current].email}</h1>
      <Flex>
        <button value="NO" onClick={handleSwipe}>
          NO
        </button>
        <button value="YES" onClick={handleSwipe}>
          YES
        </button>
      </Flex>
    </Box>
  )
}

当前问题

注释掉异步写入数据库的代码时,handleRefetchUsers()函数工作正常;但取消注释这些异步代码后,每次触发该函数都会跳过4条条目。


解决思路

问题根源

问题出在React状态更新的异步特性和闭包捕获旧值:

  1. handleSwipeType里的异步请求(axios.post)会等待执行完成,这段时间内组件可能已经重新渲染,但函数内捕获的skip还是触发时的旧值。
  2. handleRefetchUsers直接使用skip + FETCH_USERS_PAGINATION_LIMIT更新状态,当异步操作完成后调用该函数,闭包中的skip是旧值,导致多次累加或跳步。

修复方案

1. 使用函数式更新获取最新状态

修改handleRefetchUsers,通过函数式更新获取最新的skip值,避免闭包捕获旧值:

const handleRefetchUsers = () => {
  setCurrent(0);
  // 用函数式更新拿到最新的skip状态
  setSkip(prevSkip => {
    const newSkip = prevSkip + FETCH_USERS_PAGINATION_LIMIT;
    setUrl(`/api/swipes/get-users/?userId=${user?.id}&skip=${newSkip}`);
    refetch();
    return newSkip;
  });
};

2. 提前保存状态,避免异步期间状态变化影响判断

在执行异步操作前,先判断并保存当前是否为最后一条用户,避免异步操作期间状态变化导致判断错误:

const handleSwipeType = async (type: string, id: string) => {
  const values = {
    userSwipeOn: id,
    currentUser: userId,
  };

  // 提前保存是否为最后一条的判断结果
  const isLastUser = current === data.users.length - 1;

  // 执行异步写入操作
  if (type === 'YES') {
    if (current && data?.users[current]?.isMatch) {
      await axios.post('/api/swipes/create-match', values);
      alert('You have a match!');
    }
    await axios.post('/api/swipes/like', values);
  } else {
    await axios.post('/api/swipes/dislike', values);
  }

  // 用提前保存的结果判断,不受异步期间状态变化影响
  if (isLastUser) {
    handleRefetchUsers();
  } else {
    // 同样用函数式更新获取最新的current值
    setCurrent(prev => prev! + 1);
  }
};

3. 优化useQuery调用,自动触发请求

因为useQuery的queryKey包含url,当url更新时,useQuery会自动重新发起请求,所以可以去掉手动调用refetch(),让useQuery自动处理:

const handleRefetchUsers = () => {
  setCurrent(0);
  setSkip(prevSkip => {
    const newSkip = prevSkip + FETCH_USERS_PAGINATION_LIMIT;
    setUrl(`/api/swipes/get-users/?userId=${user?.id}&skip=${newSkip}`);
    return newSkip;
  });
};

这样当url更新后,queryKey变化,useQuery会自动触发新的请求,无需手动调用refetch(),减少重复操作。


内容的提问来源于stack exchange,提问作者Ben Shekhtman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 13:15:51