Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 290
user210757 Avatar asked Jul 02 '26 18:07

user210757


2 Answers

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.

like image 120
user210757 Avatar answered Jul 05 '26 08:07

user210757


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"])

like image 26
TkDodo Avatar answered Jul 05 '26 08:07

TkDodo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!