HuolalaTech / react-query-kit

🕊️ A toolkit for ReactQuery that make ReactQuery hooks reusable and typesafe
MIT License
355 stars 11 forks source link

createQuery with only function as variable causes stale state #24

Closed ryan-ju closed 1 year ago

ryan-ju commented 1 year ago

Description

When using createQuery, if I only pass a function (e.g., getAccessToken() that returns the latest token) as variable to queryFn, then the resulting query does not refresh when the component refreshes (e.g., due to state change).

This problem occurs if the query is initilally enabled: false, and later enabled.

Example code (sandbox link)

import React, { createContext, useContext, useState } from "react";
import ReactDOM from "react-dom";
import {
  useQuery,
  QueryClient,
  QueryClientProvider
} from "@tanstack/react-query";
import { createQuery } from "react-query-kit";

import "./styles.css";

class Data {
  constructor(data) {
    this.data = data;
  }

  getData = () => {
    return this.data;
  };
}

const useMyQuery = createQuery({
  primaryKey: "my-query",
  queryFn: ({ queryKey }) => {
    console.log(
      ">>> [useMyQuery.queryFn] data =",
      queryKey[1].data,
      ", getData._version =",
      queryKey[1].getData._version,
      ", getData() =",
      queryKey[1].getData()
    );
    return "don't care";
  }
});

const WrapperContext = createContext({});

let version = 0;

const WrapperProvider = ({ children }) => {
  const [data, setData] = useState(new Data(""));

  const getData = () => {
    return data.getData();
  };

  getData._version = version++;

  return (
    <WrapperContext.Provider
      value={{
        data,
        getData,
        setData
      }}
    >
      {children}
    </WrapperContext.Provider>
  );
};

let counter = 0;

const Component = () => {
  const { data, getData, setData } = useContext(WrapperContext);

  const query = useQuery({
    queryKey: ["todos", getData],
    queryFn: () => {
      console.log(
        ">>> [useQuery.queryFn] getData._version =",
        getData._version,
        ", getData =",
        getData()
      );
      return "don't care";
    },
    cacheTime: 0,
    enabled: data.data !== ""
  });

  const myQuery = useMyQuery({
    // Option 1: this print stale data 
    variables: {
      getData: data.getData
    },
    // Option 2: this prints up-to-date data
    // variables: {
    //   data,
    //   getData: data.getData,
    // },
    cacheTime: 0,
    enabled: data.data !== ""
  });

  const onClick = (e) => {
    setData(new Data(counter++));
  };

  return (
    <div>
      <h2>data = {data.data}</h2>
      <button onClick={onClick}>Update data</button>
    </div>
  );
};

// Create a client
const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <WrapperProvider>
        <div className="App">
          <Component />
        </div>
      </WrapperProvider>
    </QueryClientProvider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Expected

Code in Option 1 and Option 2 should give the same result.

Actual

Code in Option 1 gives stale value. Option 2 adds an object data to the variables, and it prints the up-to-date value.

liaoliao666 commented 1 year ago

First, Do not pass function into option variables. Second, Your tow options are diffrent. If you convert Option 1 to the following code then will get same result.

 const query = useQuery({
    queryKey: ["todos", {getData}],
    queryFn: ({queryKey}) => {
      console.log(
        ">>> [useQuery.queryFn] data =",
        queryKey[1].data,
        ", getData._version =",
        queryKey[1].getData._version,
        ", getData() =",
        queryKey[1].getData()
      );
      return "don't care";
    },
    cacheTime: 0,
    enabled: data.data !== ""
  });