Advanced patterns for optimistic updates #2362
-
Hi 👋 , The following patterns happens very often in our application that we start doing the following when we need optimistic cache. Assume the newsletter resource is paginated we we possibly have multiple // Placed somewhere easily accessible
export const useUpdateNewsletterCache = () => {
const queryClient = useQueryClient();
const queryCache = queryClient.getQueryCache();
const updateCache = useCallback(
(newsletterId: Newsletter['id'], updater: (newsletter: Newsletter) => Newsletter) => {
const previousNewsletters = queryCache.findAll(QueryKeys.newsletters.all(), { exact: false });
previousNewsletters.forEach(list => {
queryClient.setQueryData<ApiResponse<Newsletter[]>>(list.queryKey, old => ({
...old!,
data: (old?.data || []).map(newsletter =>
newsletter.id === newsletterId ? updater(newsletter) : newsletter,
),
}));
});
return [...previousNewsletters];
},
[queryCache, queryClient],
);
return updateCache;
}; This allows to perform optimistic updates on a single instances of the const queryClient = useQueryClient();
const updateCache = useUpdateNewsletterCache();
const { mutateAsync, isLoading } = useMutation(createNewsletterItem, {
onMutate: ({
newsletterId,
params,
}: {
newsletterId: Newsletter['id'];
params: NewsletterItemParams;
}) => {
const previousNewsletters = updateCache(newsletterId, newsletter => {
return {
...newsletter,
items: [
...newsletter.items,
params
},
],
};
});
return { previousNewsletters };
},
onSuccess: (response, { newsletterId }) => {
// Yay!
},
onError: (_d, _p, context) => {
if (context?.previousNewsletters) {
// A list of queries we loop over
context.previousNewsletters.forEach(query => {
if (query) {
queryClient.setQueryData(query.queryKey, query.revertState?.data);
}
});
}
},
}); Now notice the |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
I think I can answer my own question, don't ;) The revertState is really the last state of the successful query, so if you first did the query the state will be undefined. I also noticed a bug in the code because I pass around Query objects which get mutated, its safer to extract the data + query key from the list and pass that around instead export const useUpdateNewsletterCache = () => {
const queryClient = useQueryClient();
const queryCache = queryClient.getQueryCache();
const updateCache = useCallback(
(newsletterId: Newsletter['id'], updater: (newsletter: Newsletter) => Newsletter) => {
const previousQueries = queryCache.findAll(QueryKeys.newsletters.all(), { exact: false });
let previousData: [QueryKey, unknown][] = [];
previousQueries.forEach(list => {
previousData.push([list.queryKey, list.state.data]);
queryClient.setQueryData<ApiResponse<Newsletter[]>>(list.queryKey, old => ({
...old!,
data: (old?.data || []).map(newsletter =>
newsletter.id === newsletterId ? updater(newsletter) : newsletter,
),
}));
});
return previousData;
},
[queryCache, queryClient],
);
return updateCache;
}; |
Beta Was this translation helpful? Give feedback.
I think I can answer my own question, don't ;)
The revertState is really the last state of the successful query, so if you first did the query the state will be undefined. I also noticed a bug in the code because I pass around Query objects which get mutated, its safer to extract the data + query key from the list and pass that around instead