Re-fetching with useQuery on argument change

Viewed 303

I am trying to implement pagination using react-query. On page change I am updating the page inside useEffect using setArrivals. For some reason I am always sending the previous value of the arrivals as the argument for the getProductList function. To fix the issue I am sending the refetch() request inside the setTimeout. It does work but it doesn't feel right to me. Let me know what I am doing wrong.

const HomePage = ({ newArrivals }) => {
  const [page, setPage] = useState(1);
  const [arrivals, setArrivals] = useState({ ...newArrivals, page: page });

  useEffect(() => {
    setArrivals((values) => {
      console.log({ page });
      return { ...values, page: page };
    });
    setTimeout(function () {
      newArrivalsQuery.refetch();
    }, 0);
  }, [page]);

  const newArrivalsQuery = useQuery(
    ['productListByNewArrivals'],
    () => getProductList(arrivals),
    {
      select: useCallback((data) => {
        return JSON.parse(data);
      }, []),
    }
  );

  return (
    <>
      <NewArrivals
        newArrivalsQuery={newArrivalsQuery}
        page={page}
        setPage={setPage}
      />
    </>
  );
};

export async function getServerSideProps(context) {
  const newArrivals = {
    sort: 'createdAt',
    order: 'desc',
  };

  try {
    const queryClient = new QueryClient();

    await queryClient.prefetchQuery('productListByNewArrivals', async () => {
      const newArrivalsResult = await listProduct(newArrivals);
      return JSON.stringify(newArrivalsResult);
    });

    return {
      props: {
        newArrivals: newArrivals,
        dehydratedState: dehydrate(queryClient),
      },
    };
  } catch (error) {
    console.log('error: ', error);
    if (error) {
      return {
        redirect: {
          destination: '/login',
          permanent: false,
        },
      };
    }
  }
}
1 Answers

The best way is to add the dependencies of your query function to the queryKey. react-query is declarative and will re-fetch automatically if the queryKey changes. If you have to reach to useEffect and refetch, it's likely not the easiest solution:

const HomePage = ({ newArrivals }) => {
  const [page, setPage] = useState(1);
  const [arrivals, setArrivals] = useState({ ...newArrivals, page: page });

  const newArrivalsQuery = useQuery(
    ['productListByNewArrivals', arrivals],
    () => getProductList(arrivals),
    {
      select: useCallback((data) => {
        return JSON.parse(data);
      }, []),
    }
  );

now, arrivals are part of the queryKey, which is what you are using in the queryFn in getProductList. Now all you need to do is call setArrivals and react-query will refetch.

Side note: it looks like arrivals is not really state, but derived state. At least in this snippet, you only call the setter in an effect, which seems wrong. It looks like you want to keep in in-sync with page and compute it every time you call setPage, so you can also do:

  const [page, setPage] = useState(1);
  const arrivals = { ...newArrivals, page: page };
Related