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

React Query:相同Payload调用setState时不触发API请求的问题

解决React Query相同筛选条件下点击应用按钮不触发API的问题

问题场景

将项目从redux-saga迁移到React Query时,交易历史页面的筛选功能存在以下问题:

  • 初始加载或切换不同筛选条件时,React Query能正常触发API请求;
  • 当用户选择与当前相同的筛选条件并点击应用按钮时,由于queryKey依赖的payload内容未发生变化,React Query不会重新发起API请求;
  • 即使通过扩展对象强制修改payload的引用,问题仍未解决。

需要实现:无论筛选条件是否与当前一致,点击应用按钮都能触发API获取最新数据。

可行解决方案

方案1:直接调用refetch方法

从useQuery的返回值中解构出refetch函数,在点击应用按钮时主动调用,强制触发API请求。

修改后代码示例:

import './App.css';
import TodoCard from './components/TodoCard';
import { todo } from './types/todo';
import { QueryClient, useQuery } from '@tanstack/react-query';
import React from 'react';

const queryClient = new QueryClient();

function App2() {
  const [payload, setState] = React.useState({ type: 1 });

  // 从queryKey中获取参数,避免闭包导致的旧值问题
  const fetchTodos = async ({ queryKey }) => {
    const [_, currentPayload] = queryKey;
    console.log("State received is at fetchtodos", currentPayload);
    
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/todos?_page=${currentPayload.type}`
    );
    return res.json();
  };

  const {
    isFetching,
    data,
    status,
    error,
    refetch // 解构refetch方法
  } = useQuery({
    queryKey: ['todos', payload],
    queryFn: fetchTodos,
    staleTime: 0,
    gcTime: Infinity,
    placeholderData: [],
  });

  const content = data && data.map((todo: todo) => (
    <TodoCard key={todo.id} todo={todo} />
  ));

  if (status === 'error') {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div className='app'>
      <button onClick={() => {
        setState((state) => ({ ...state, type: 2 }));
        refetch(); // 强制触发重新请求
      }}>Press</button>
      {isFetching && <h3>Loading...</h3>}
      {!isFetching && content}{' '}
    </div>
  );
}

export default App2;

方案2:在queryKey中加入刷新计数器

维护一个额外的状态refreshCount,每次点击应用按钮时递增该计数器,并将其加入queryKey。即使payload未变化,queryKey也会因计数器变化而更新,触发React Query重新请求。

修改后代码示例:

import './App.css';
import TodoCard from './components/TodoCard';
import { todo } from './types/todo';
import { QueryClient, useQuery } from '@tanstack/react-query';
import React from 'react';

const queryClient = new QueryClient();

function App2() {
  const [payload, setState] = React.useState({ type: 1 });
  const [refreshCount, setRefreshCount] = React.useState(0); // 新增刷新计数器

  const fetchTodos = async ({ queryKey }) => {
    const [_, currentPayload] = queryKey;
    console.log("State received is at fetchtodos", currentPayload);
    
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/todos?_page=${currentPayload.type}`
    );
    return res.json();
  };

  const {
    isFetching,
    data,
    status,
    error,
  } = useQuery({
    queryKey: ['todos', payload, refreshCount], // 加入计数器到queryKey
    queryFn: fetchTodos,
    staleTime: 0,
    gcTime: Infinity,
    placeholderData: [],
  });

  const content = data && data.map((todo: todo) => (
    <TodoCard key={todo.id} todo={todo} />
  ));

  if (status === 'error') {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div className='app'>
      <button onClick={() => {
        setState((state) => ({ ...state, type: 2 }));
        setRefreshCount(prev => prev + 1); // 递增计数器
      }}>Press</button>
      {isFetching && <h3>Loading...</h3>}
      {!isFetching && content}{' '}
    </div>
  );
}

export default App2;

方案3:使用invalidateQueries标记数据过期

通过useQueryClient获取queryClient实例,在点击按钮时调用invalidateQueries,让React Query标记对应查询为过期,自动重新发起请求。

修改后代码示例:

import './App.css';
import TodoCard from './components/TodoCard';
import { todo } from './types/todo';
import { QueryClient, useQuery, useQueryClient } from '@tanstack/react-query';
import React from 'react';

const queryClient = new QueryClient();

function App2() {
  const [payload, setState] = React.useState({ type: 1 });
  const queryClient = useQueryClient(); // 获取queryClient实例

  const fetchTodos = async ({ queryKey }) => {
    const [_, currentPayload] = queryKey;
    console.log("State received is at fetchtodos", currentPayload);
    
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/todos?_page=${currentPayload.type}`
    );
    return res.json();
  };

  const {
    isFetching,
    data,
    status,
    error,
  } = useQuery({
    queryKey: ['todos', payload],
    queryFn: fetchTodos,
    staleTime: 0,
    gcTime: Infinity,
    placeholderData: [],
  });

  const content = data && data.map((todo: todo) => (
    <TodoCard key={todo.id} todo={todo} />
  ));

  if (status === 'error') {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div className='app'>
      <button onClick={() => {
        setState((state) => ({ ...state, type: 2 }));
        // 标记对应查询为过期,触发重新请求
        queryClient.invalidateQueries({ queryKey: ['todos', payload] });
      }}>Press</button>
      {isFetching && <h3>Loading...</h3>}
      {!isFetching && content}{' '}
    </div>
  );
}

export default App2;

注意事项

  • 建议从queryKey中获取请求参数,避免直接使用外部state导致的闭包问题(原代码中fetchTodos直接引用payload可能会拿到旧值);
  • 三种方案可根据场景选择:refetch适合明确手动触发的场景,计数器方案适合需要自动关联状态变化的场景,invalidateQueries适合批量失效多个查询的场景。

内容的提问来源于stack exchange,提问作者Divya Prakash

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:05:23