2

The API that I hit with React-Query returns a set of data and a hash key for this data in a form like so:

{
orders:[{order1},{order2},....]
dataHash:number
}

I Use this dataHash to check if the request results are equal inside useQuery() options to make it easy for reach-query to compare big datasets on it's own like so:

isDataEqual:(oldData, newData)=>oldData?.dataHash === newData.dataHash, //do not refresh page if hashes are equal

The API also has a feature that allows not to send a dataset if the dataHash matches with the one in a request. In such case it only returns the following:

{
orders:[] //empty array here
dataHash:number
}

But since my compare function only looks at dataHash the new data will not be used and everything will work as expected.

The problem I have currently is that I cannot seem to find a built-in way in react-query to save the dataHash from the old request so that it is being passed as a parameter into the new one. Ideally I would like to see react-query passing the old data into the request function so I can compare it myself but this is not an option.

I also found that it passes some sort of metadata object into the request query, but I have not found a way to mutate this object for any particular request. Am I missing Something here?

  const getOrders = async (metadata:any): Promise<ShowOrdersResponse> => {
    console.log(metadata) //this holds the "meta" object and query keys
   
    const request: ShowOrdersRequest = {
      searchParam: mySearchParam,
      dataHash: 12//dataHash
    };

    const {data} = await secureAxios.post<ShowOrdersResponse>(ApiEndpoints.ShowOrders, request)
    return data;
  }

  const {data} = useQuery<ShowWorkOrdersResponse>(['workorders', mySearchParam], getOrders, {
    cacheTime: 30000,
    refetchInterval: 5000,
    isDataEqual:(oldData, newData)=>oldData?.dataHash === newData.dataHash, //do not refresh page if hashes are equal, this works as charm
    meta:{dataHash:555}  //I am able to pass this "meta" object into the request query, but cannot mutate it
  });

Edit1: I implemented the proposed solution but it still does not solve the problem completely. Currently I am saving the returned dataHash from server inside the component state like so:

onSuccess: (data) => setDataHash(data?.data.dataHash)

Later I call useQuery() with different search params, which it considers as a totally different request, yet since I'm sending the same hash I get no results back. isDataEqual() returns false and updates the data with empty result.

I think I need an ability to save the dataHash per-request(starting with undefined) but this means that queryFn needs to have access to "oldData.data.dataHash" to send it in. and currently i do not see how this can be done.

2 Answers 2

1

whatever you return from the queryFn will be put into the query cache, and will thus be available as data returned from useQuery as well as on the isDataEqual function (because that gets passed the same data). So I would do:

const getOrders = async (): Promise<ShowOrdersResponse> => {
   
    const request: ShowOrdersRequest = {
      searchParam: mySearchParam,
      dataHash: 12
    };

    const { data } = await secureAxios.post<ShowOrdersResponse>(ApiEndpoints.ShowOrders, request)
    return {
      data,
      dataHash
    }
  }

useQuery(
  ['workorders', mySearchParam],
  getOrders,
  {
    isDataEqual:(oldData, newData) => oldData?.dataHash === newData.dataHash,
  }
)

of course, one drawback would be that you'd need to unwrap data.data then in your component, but you can work around that with the select function:

useQuery(
  ['workorders', mySearchParam],
  getOrders,
  {
    isDataEqual:(oldData, newData) => oldData?.dataHash === newData.dataHash,
    select: data => data.data
  }
)

this works because isDataEqual gets passed the data from the cache directly, while each component (observer) gets passed the data after running through select. So basically, you would only pass the dataHash through so that you can use it in isDataEqual.

Sign up to request clarification or add additional context in comments.

5 Comments

I have not been able to figure out how to properly use the "select" with the typescript implementation, so I ended up manually unwrapping the data, but aside from that solution works great. Thanks!
I can take a look at a codesandbox example if you have one. I think what works best is to explicitly type annotate the data that goes into the select function.
I edited the question to describe my most recent delema
I don't understand why you would need the onSuccess with the proposed solution. The dataHash for each query would be stored in the query cache alongside the actual data. In the queryFn, you can get the old data via queryClient.getQueryData(queryKey), and the queryKey is injected via the queryFunctionContext. You can skip doing the request if the query hash is the same if that is what you want.
using queryClient.getQueryData(queryKey) was a key here. thanks a lot!
0

My solution was to modify queryFn so that it receives cached data as a second optional paramter and returns it whenever appropriate. See details in the related question.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.