Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/DogPawHat/test-synched-invalidation. Created with StackBlitz ⚡️ stackblitz.com/edit/vitejs-vite-cftahzzq
Something went wrong. Try again.
4.3 kB · 172 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173import { queryOptions, useSuspenseQueries, notifyManager, defaultScheduler, useQueryClient, useMutation,} from '@tanstack/react-query';import { useRef, useCallback } from 'react';
let numbers = 1;let letters = 96;
const sleep = (ms: number) => new Promise((resolve) => { setTimeout(resolve, ms); });
const numbersQueryOptions = () => queryOptions({ queryKey: ['numbers'], queryFn: async () => { console.log('Numbers fetching'); numbers += 1; await sleep(500); console.log('Numbers done'); return numbers; }, });
const lettersQueryOptions = () => queryOptions({ queryKey: ['letters'], queryFn: async () => { console.log('Letters fetching'); letters += 1; await sleep(3000); console.log('Letters done'); return String.fromCharCode(letters); }, });
export const useUnsyncedMutation = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: () => Promise.resolve(), onSuccess: async () => { await Promise.all([ queryClient.invalidateQueries({ queryKey: numbersQueryOptions().queryKey, }), queryClient.invalidateQueries({ queryKey: lettersQueryOptions().queryKey, }), ]); }, });};
const useSyncedMutation = () => { const schedulerRef = useRef<'unlocked' | 'locked'>('unlocked'); const promiseSetRef = useRef(new Set<Symbol>()); const resolveSchedulerUnlockRef = useRef<(_: unknown) => void | null>(null); const rejectSchedulerLockRef = useRef<(_: unknown) => void | null>(null); const queryClient = useQueryClient();
const lock = () => { schedulerRef.current = 'locked'; let promise = new Promise((res, rej) => { resolveSchedulerUnlockRef.current = res; rejectSchedulerLockRef.current = rej; });
notifyManager.setScheduler(async (cb) => { await promise; cb(); schedulerRef.current = 'unlocked'; resolveSchedulerUnlockRef.current = null; rejectSchedulerLockRef.current = null; notifyManager.setScheduler(defaultScheduler); }); };
const synced = async (getPromise: () => Promise<unknown>) => { if (schedulerRef.current === 'unlocked') { lock(); }
const promiseId = Symbol('promise'); promiseSetRef.current.add(promiseId);
try { await getPromise(); } catch (e) { (rejectSchedulerLockRef.current as (_: unknown) => void)(e); } finally { promiseSetRef.current.delete(promiseId); if (promiseSetRef.current.size === 0) { (resolveSchedulerUnlockRef.current as (_: unknown) => void)(undefined); } } };
return useMutation({ mutationFn: () => Promise.resolve(), onSuccess: async () => { await synced(async () => { return Promise.allSettled([ queryClient.invalidateQueries({ queryKey: numbersQueryOptions().queryKey, }), queryClient.invalidateQueries({ queryKey: lettersQueryOptions().queryKey, }), ]); }); }, });};
export const App = () => { const [numbersQuery, lettersQuery] = useSuspenseQueries({ queries: [numbersQueryOptions(), lettersQueryOptions()], }); const { mutate: mutateUnsynced } = useUnsyncedMutation(); const { mutate: mutateSynced } = useSyncedMutation();
return ( <div> <section> <h2>Numbers query</h2> <pre> {JSON.stringify( { status: numbersQuery.status, data: numbersQuery.data, }, null, 2 )} </pre>
<h2>Letters query</h2> <pre> {JSON.stringify( { status: lettersQuery.status, data: lettersQuery.data, }, null, 2 )} </pre>
<button onClick={() => { mutateUnsynced(); }} > Invalidate </button>
<button onClick={() => mutateSynced()}>Invalidate (synced)</button>
<p> To reproduce the race condition : click the synced button after 600ms (before 3s) </p> </section> </div> );};