Should the arguments passed to react-query's useQuery hook be referentially stable?

Viewed 185

I am going through an existing codebase, looking at some data fetching hooks that use useQuery

I am noticing that, almost everywhere, care is taken so that the arguments passed in the useQuery hook (and other similar react-query hooks) are referentially the same between hook executions.

For example

//custom_data_fetching_hook.js

import { useQuery } from 'react-query';

function fetchSomeStuff() {
  // server data request implementation
}

const queryOptions = {
 // object as described in the 3rd argument here:
 // https://react-query.tanstack.com/reference/useQuery
};

export function useSomeServerStuff() {
  return useQuery(['query key'], fetchSomeStuff, queryOptions);
}

My question is this:

Is it necessary to ensure referential stability for the useQuery, useMutation, etc, arguments? As far as I could tell the docs do not say you should do this.

Would the react-query hooks redo calculations on every component re-render if their arguments are not referentially the same between renders?

Would the react-query hooks return some referentially different items within their return object value if their arguments are not referentially the same between renders? (for example the mutate function inside the return object of useMutation)

1 Answers

To the best of my knowledge: No, the arguments do not need to be referentially stable.

Now to the more wordy part of my answer: I can only answer this based on my observations; neither do I know the internals of react-query (so maybe @TkDodo comes along and refutes my answer or points out some hidden ramifications, in that case: go with his answer) nor can I point you to any section of the docs. I'm using react-query for 6 months now in a project. I regularly watch the network tab of the browser's dev tools. We frequently use the hooks this way:

const { data } = useQuery(
  ['productdetails', itemID],
  () => api.getProduct(itemID),
  { retry: false },
);

And as you can see, the arguments are inlined; with each re-rendering the arrow function is a new function instance, and the options object is a new object instance. This does not cause any re-fetching. To my understanding the contents of the query keys array will be equality-compared to their previous counterparts, and when one of them changed the data is refetched. (Or some other code invalidates the query explicitly, which will also trigger a refetch.)

But it can still make sense to move the options into a constant or to reference a stable fetch function: I sometimes do this to improve the readability, clarity, or cleanliness. It is often satisfying to move anything that is not really dynamic out of the render function, and thus seeing the render function getting really lean and slim to the point where it completely fits into the screen without scrolling.

Related