最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Make code execute only on the first react-query success - Stack Overflow

programmeradmin4浏览0评论

I have a weird case, where I need to allow for standard useQuery behavior, but at the same time need to run a piece of code only on the first onSuccess.

I know I can achieve this with useRef, but is there a way around it?

I wasn't able to reproduce the issue 1:1, but you can see it after the first increment counter gets re-set to 100.

  const [counter, setCounter] = useState(0);
  const { data, isLoading } = useQuery(
    ["key"],
    () => {
      return axios.get("/200");
    },
    {
      onSuccess() { // this runs twice
        setCounter(100);
        console.log("fetch");
      }
    }
  );

  return (
    <div className="App">
      <button
        onClick={() => {
          setCounter(counter + 1);
        }}
      >
        counter {counter}
      </button>
      {data?.data && "data is present"}
    </div>
  );

=/src/App.tsx

I have a weird case, where I need to allow for standard useQuery behavior, but at the same time need to run a piece of code only on the first onSuccess.

I know I can achieve this with useRef, but is there a way around it?

I wasn't able to reproduce the issue 1:1, but you can see it after the first increment counter gets re-set to 100.

  const [counter, setCounter] = useState(0);
  const { data, isLoading } = useQuery(
    ["key"],
    () => {
      return axios.get("https://picsum.photos/200");
    },
    {
      onSuccess() { // this runs twice
        setCounter(100);
        console.log("fetch");
      }
    }
  );

  return (
    <div className="App">
      <button
        onClick={() => {
          setCounter(counter + 1);
        }}
      >
        counter {counter}
      </button>
      {data?.data && "data is present"}
    </div>
  );

https://codesandbox.io/s/react-query-once-66lows?file=/src/App.tsx

Share Improve this question asked Oct 14, 2022 at 16:11 Alex IronsideAlex Ironside 5,07914 gold badges76 silver badges134 bronze badges 2
  • I'm wondering what the use-case for something like this would be in practice? We have a dataUpdateCount internally that you could check for being equal to 1 if we were to expose that on the result of useQuery. I'd just be curious to know why that would be needed ? – TkDodo Commented Oct 15, 2022 at 8:09
  • @TkDodo I might be approaching this wrong, but long story short, I have an array of items that's returned from the b/e. The array is then built into a tree, which the user can edit. So first data returned from the query gets set as expanded tree elements (in our case 1 level deep), which the user can then close or open further. If I don't check, the expanded array will always change, since the user gets data from the b/e. I might have some re-render issues with the ponent, but until this is solved (assuming it's a re-render issue), I need a workaround. – Alex Ironside Commented Oct 15, 2022 at 18:40
Add a ment  | 

5 Answers 5

Reset to default 2

The easy hack is to add another boolean state like const [isRan, switchIsRan] = useState(false) and then check its value in onSuccess() before running any logic and also switch it to true at the end of that callback.

Use lodash's once Like this

  const [counter, setCounter] = useState(0);
  const onSuccess = _.once(() => {
        setCounter(100);
        console.log("fetch"));
      }
  const { data, isLoading } = useQuery(
    ["key"],
    () => {
      return axios.get("https://picsum.photos/200");
    },
    {
      onSuccess
    }
  );

  return (
    <div className="App">
      <button
        onClick={() => {
          setCounter(counter + 1);
        }}
      >
        counter {counter}
      </button>
      {data?.data && "data is present"}
    </div>
  );

I run into a similar issue a few days ago and found this GH thread that suggest this solution that did the trick for me:

const [counter, setCounter] = useState(0);

const { data, isLoading } = useQuery(
    ["key"],
    () => axios.get("https://picsum.photos/200").then(res => {
       // This will only run once
       setCounter(100);
       return res;
    })
  );

  return (
    <div className="App">
      <button
        onClick={() => {
          setCounter(counter + 1);
        }}
      >
        counter {counter}
      </button>
      {data?.data && "data is present"}
    </div>
  );

After a while, we found a better solution. We can simply set staleTime to Infinity.

https://react-query-v3.tanstack./reference/useQuery#_top

This allows us to still invalidate the data and refetch if needed, but until it's invalidated, the query will not refetch

https://react-query-v3.tanstack./guides/query-invalidation#query-matching-with-invalidatequeries

We might also need to disable refetching on window focus

I had a similar need (query gets updated by user in WYSIWYG editor) and needed to get the initial data once only.

Instead of using the onSuccess callback, I did something like:

const [initialData, setInitialData] = useState(false);
const { data, isLoading } = useQuery(...);
// sets initialData if not set yet and data has been fetched
if (!initialData && !isLoading && data) {
  setInitialData(data);
}
发布评论

评论列表(0)

  1. 暂无评论