user210757
user210757

Reputation: 7386

react-query getting old data

I have a master page that is a list of items, and a details page where I fetch and can update an Item. I have the following hooks based upon the react-query library:

const useItems = (options) => useQuery(["item"], api.fetchItems(options)); // used by master page
const useItem = id => useQuery(["item", id], () => api.fetchItem(id)); // used by details page
const useUpdateItem = () => {
  const queryClient = useQueryClient();
  return useMutation(item => api.updateItem(item), {
    onSuccess: ({id}) => {
     queryClient.invalidateQueries(["item"]);
     queryClient.invalidateQueries(["item", id]);
   }
  });
};

The UpdatePage component has a form component that takes a defaultValue and loads that into it's local "draft" state - so it's sort of "uncontrolled" in that respect, I don't hoist the draft state.

// UpdatePage

const query = useItem(id);
const mutation = useUpdateItem();

return (
  {query.isSuccess && 
   !query.isLoading && 
   <ItemForm defaultValue={query.data} onSubmit={mutation.mutate} />
  }
);

The problem is after I update, go to Master page, then back to Details page, the "defaultValue" gets the old item before the query completes. I do see it hitting the API in the network and the new value coming back but it's too late. How do I only show the ItemForm after the data is re-queried? Or is there a better pattern?

Upvotes: 8

Views: 19362

Answers (3)

FocusCookie
FocusCookie

Reputation: 34

I had the same issue today. After scanning your code it could be the same issue.

const useItem = id => useQuery(["item", id], () => api.fetchItem(id)); // used by details page

The name of the query should be unique. But based on you details the ID changes depends on the item. By that you call the query "item" with different IDs. There for you will get the cached data back if you have done the first request.

The solution in my case was to write the query name like this:

[`item-${id}`...]

Upvotes: -1

user210757
user210757

Reputation: 7386

My updateItem API function returns the single updated item from the server.

I used setQueryData to solve this.

const useUpdateItem = () => {
  const queryClient = useQueryClient();
  // Note - api.updateItem is return the single updated item from the server
  return useMutation(item => api.updateItem(item), {
    onSuccess: data => {
     const { id } = data;
     // set the single item query
     queryClient.setQueryData('item', id], data);
     // set the item, in the all items query
     queryClient.setQueryData(
          ['item'],
          // loop through old. if this item replace, otherwise, don't
          old => {
            return old && old.map(d => (d.id === id ? data : d));
          }
        );
   }
  });
};

I will say, react-query is picky about the key even if it is fuzzy. Originally my id was from the url search params and a string, but the item coming back from the db an int, so it didn't match. So a little gotcha there.

Also, when I go back to the Master list page, I see the item change, which is kind of weird to me coming from redux. I would have thought it was changed as soon as I fired the synchronous setQueryData. Because I'm using react-router the "pages" are complete remounted so not sure why it would load the old query data then change it.

Upvotes: 2

TkDodo
TkDodo

Reputation: 29046

isLoading will only be true when the query is in a hard loading state where it has no data. Otherwise, it will give you the stale data while making a background refetch. This is on purpose for most cases (stale-while-revalidate). Your data stays in the cache for 5 minutes after your detail view unmounts because that’s the default cacheTime.

Easiest fix would just set that to 0 so that you don’t keep that data around.

You could also react to the isFetching flag, but this one will always be true when a request goes out, so also for window focus refetching for example.

Side note: invalidateQueries is fuzzy per default, so this would invalidate the list and detail view alike: queryClient.invalidateQueries(["item"])

Upvotes: 1

Related Questions