1- import { type InfiniteData , InfiniteQueryObserver , type QueryClient , type QueryState } from '@tanstack/react-query' ;
1+ import { type InfiniteData , InfiniteQueryObserver , type QueryClient , QueryObserver , type QueryState } from '@tanstack/react-query' ;
22import { queryMatchesTag } from './operateOnTags' ;
33import type { QueryTagContext , QueryUpdateTag } from './types' ;
44import { getUpdater } from './updaters' ;
55
6- export type UpdateTagsUndoer = { hash : string ; data : unknown } ;
6+ export type UpdateTagsUndoer = {
7+ hash : string ;
8+ data : unknown ;
9+ newData : unknown ;
10+ subscribe ( ) : void ;
11+ dispose ( ) : void ;
12+ undo ( ) : void ;
13+ } ;
714
815/**
916 * Works similar to invalidateTags, but instead of invalidating queries, it updates them with the provided updater or the data resulting from the mutation.
@@ -43,36 +50,82 @@ export function updateTags({
4350 const willInvalidate = typeof tag !== 'object' || [ 'post' , 'both' ] . includes ( tag . invalidate || 'both' ) ;
4451
4552 for ( const q of list ) {
46- undos . push ( { hash : q . queryHash , data : q . state . data } ) ;
47-
48- let newData : unknown ;
49- if ( q . observers [ 0 ] && q . observers [ 0 ] instanceof InfiniteQueryObserver ) {
50- const data = q . state . data as InfiniteData < unknown > ;
51- if ( data . pages && Array . isArray ( data . pages ) ) {
52- newData = {
53- ...data ,
54- pages : data . pages . map ( ( page ) => updaterFn ( ctx , page ) ) ,
55- } as InfiniteData < unknown > ;
53+ let isInfinite = false ;
54+
55+ function getNewData ( ) {
56+ if ( ! updaterFn ) return undefined ;
57+
58+ let newData : unknown ;
59+ if ( q . observers [ 0 ] && q . observers [ 0 ] instanceof InfiniteQueryObserver ) {
60+ isInfinite = true ;
61+ const data = q . state . data as InfiniteData < unknown > ;
62+ if ( data . pages && Array . isArray ( data . pages ) ) {
63+ newData = {
64+ ...data ,
65+ pages : data . pages . map ( ( page ) => updaterFn ( ctx , page ) ) ,
66+ } as InfiniteData < unknown > ;
67+ }
68+ } else {
69+ newData = updaterFn ( ctx , q . state . data ) ;
5670 }
57- } else {
58- newData = updaterFn ( ctx , q . state . data ) ;
71+
72+ return newData ;
5973 }
6074
61- setDataToExistingQuery ( queryClient , q . queryHash , newData , willInvalidate ? { isInvalidated : true } : undefined , {
62- updated : optimistic ? 'optimistic' : 'pessimistic' ,
63- } ) ;
75+ const newData = getNewData ( ) ;
76+
77+ let observer : QueryObserver < any , any > | InfiniteQueryObserver < any , any > | null = null ;
78+
79+ const updateType = optimistic ? ( 'optimistic' as const ) : ( 'pessimistic' as const ) ;
80+ const meta = { updated : updateType } ;
81+
82+ let subscribePaused = false ;
83+ const undoObj : UpdateTagsUndoer = {
84+ hash : q . queryHash ,
85+ data : q . state . data ,
86+ newData,
87+ subscribe : ( ) => {
88+ subscribePaused = false ;
89+ observer = isInfinite
90+ ? new InfiniteQueryObserver ( queryClient , { ...( q . options as any ) , enabled : false } )
91+ : new QueryObserver ( queryClient , { queryKey : q . queryKey , enabled : false } ) ;
92+ observer . trackProp ( 'data' ) ;
93+
94+ q . addObserver ( observer ) ;
95+ observer . subscribe ( ( ev ) => {
96+ if ( subscribePaused ) return ;
97+
98+ const newData = getNewData ( ) ;
99+ undoObj . newData = newData ;
100+
101+ subscribePaused = true ;
102+ setDataToExistingQuery ( queryClient , undoObj . hash , newData , willInvalidate ? { isInvalidated : true } : undefined , meta ) ;
103+ subscribePaused = false ;
104+ } ) ;
105+ } ,
106+ dispose : ( ) => {
107+ if ( ! observer ) return ;
108+ q . removeObserver ( observer ) ;
109+ observer . destroy ( ) ;
110+ observer = null ;
111+ subscribePaused = true ;
112+ } ,
113+ undo : ( ) => {
114+ undoObj . dispose ( ) ;
115+ setDataToExistingQuery ( queryClient , undoObj . hash , undoObj . data , undefined , { updated : 'undone' } ) ;
116+ } ,
117+ } ;
118+ undos . push ( undoObj ) ;
119+
120+ subscribePaused = true ;
121+ setDataToExistingQuery ( queryClient , undoObj . hash , newData , willInvalidate ? { isInvalidated : true } : undefined , meta ) ;
122+ subscribePaused = false ;
64123 }
65124 }
66125
67126 return undos ;
68127}
69128
70- export function undoUpdateTags ( undos : UpdateTagsUndoer [ ] , queryClient : QueryClient ) {
71- for ( const { hash, data } of undos ) {
72- setDataToExistingQuery ( queryClient , hash , data , { } , { updated : 'undone' } ) ;
73- }
74- }
75-
76129/**
77130 * The `setQueryData` method of react-query does not take query hash fn into account.
78131 * It creates duplicate queries even though the query key is the same.
@@ -88,6 +141,6 @@ function setDataToExistingQuery(
88141 } ,
89142) {
90143 const query = queryClient . getQueryCache ( ) . get ( hash ) ;
91- query ?. setData ( newData ) ;
144+ query ?. setData ( newData , { manual : true } ) ;
92145 if ( state || meta ) query ?. setState ( state || { } , { meta } ) ;
93146}
0 commit comments