@@ -137,8 +137,8 @@ describe('React hydration', () => {
137137 queryFn : ( ) => dataQuery ( [ 'should change' ] ) ,
138138 } )
139139 await intermediateClient . prefetchQuery ( {
140- queryKey : [ 'added string ' ] ,
141- queryFn : ( ) => dataQuery ( [ 'added string ' ] ) ,
140+ queryKey : [ 'added' ] ,
141+ queryFn : ( ) => dataQuery ( [ 'added' ] ) ,
142142 } )
143143 const dehydrated = dehydrate ( intermediateClient )
144144 intermediateClient . clear ( )
@@ -147,17 +147,121 @@ describe('React hydration', () => {
147147 < QueryClientProvider client = { queryClient } >
148148 < HydrationBoundary state = { dehydrated } >
149149 < Page queryKey = { [ 'string' ] } />
150- < Page queryKey = { [ 'added string ' ] } />
150+ < Page queryKey = { [ 'added' ] } />
151151 </ HydrationBoundary >
152152 </ QueryClientProvider > ,
153153 )
154154
155- // Existing query data should be overwritten if older,
156- // so this should have changed
155+ // Existing observer should not have updated at this point,
156+ // as that would indicate a side effect in the render phase
157+ rendered . getByText ( 'string' )
158+ // New query data should be available immediately
159+ rendered . getByText ( 'added' )
160+
157161 await sleep ( 10 )
162+ // After effects phase has had time to run, the observer should have updated
163+ expect ( rendered . queryByText ( 'string' ) ) . toBeNull ( )
158164 rendered . getByText ( 'should change' )
159- // New query data should be available immediately
160- rendered . getByText ( 'added string' )
165+
166+ queryClient . clear ( )
167+ } )
168+
169+ // When we hydrate in transitions that are later aborted, it could be
170+ // confusing to both developers and users if we suddenly updated existing
171+ // state on the screen (why did this update when it was not stale, nothing
172+ // remounted, I didn't change tabs etc?).
173+ // Any queries that does not exist in the cache yet can still be hydrated
174+ // since they don't have any observers on the current page that would update.
175+ test ( 'should hydrate new but not existing queries if transition is aborted' , async ( ) => {
176+ const initialDehydratedState = JSON . parse ( stringifiedState )
177+ const queryCache = new QueryCache ( )
178+ const queryClient = createQueryClient ( { queryCache } )
179+
180+ function Page ( { queryKey } : { queryKey : [ string ] } ) {
181+ const { data } = useQuery ( {
182+ queryKey,
183+ queryFn : ( ) => dataQuery ( queryKey ) ,
184+ } )
185+ return (
186+ < div >
187+ < h1 > { data } </ h1 >
188+ </ div >
189+ )
190+ }
191+
192+ const rendered = render (
193+ < QueryClientProvider client = { queryClient } >
194+ < HydrationBoundary state = { initialDehydratedState } >
195+ < Page queryKey = { [ 'string' ] } />
196+ </ HydrationBoundary >
197+ </ QueryClientProvider > ,
198+ )
199+
200+ await rendered . findByText ( 'string' )
201+
202+ const intermediateCache = new QueryCache ( )
203+ const intermediateClient = createQueryClient ( {
204+ queryCache : intermediateCache ,
205+ } )
206+ await intermediateClient . prefetchQuery ( {
207+ queryKey : [ 'string' ] ,
208+ queryFn : ( ) => dataQuery ( [ 'should not change' ] ) ,
209+ } )
210+ await intermediateClient . prefetchQuery ( {
211+ queryKey : [ 'added' ] ,
212+ queryFn : ( ) => dataQuery ( [ 'added' ] ) ,
213+ } )
214+ const newDehydratedState = dehydrate ( intermediateClient )
215+ intermediateClient . clear ( )
216+
217+ function Thrower ( ) {
218+ throw new Promise ( ( ) => {
219+ // Never resolve
220+ } )
221+
222+ // @ts -ignore
223+ return null
224+ }
225+
226+ React . startTransition ( ( ) => {
227+ rendered . rerender (
228+ < React . Suspense fallback = "loading" >
229+ < QueryClientProvider client = { queryClient } >
230+ < HydrationBoundary state = { newDehydratedState } >
231+ < Page queryKey = { [ 'string' ] } />
232+ < Page queryKey = { [ 'added' ] } />
233+ < Thrower />
234+ </ HydrationBoundary >
235+ </ QueryClientProvider >
236+ </ React . Suspense > ,
237+ )
238+
239+ rendered . getByText ( 'loading' )
240+ } )
241+
242+ React . startTransition ( ( ) => {
243+ rendered . rerender (
244+ < QueryClientProvider client = { queryClient } >
245+ < HydrationBoundary state = { initialDehydratedState } >
246+ < Page queryKey = { [ 'string' ] } />
247+ < Page queryKey = { [ 'added' ] } />
248+ </ HydrationBoundary >
249+ </ QueryClientProvider > ,
250+ )
251+
252+ // This query existed before the transition so it should stay the same
253+ rendered . getByText ( 'string' )
254+ expect ( rendered . queryByText ( 'should not change' ) ) . toBeNull ( )
255+ // New query data should be available immediately because it was
256+ // hydrated in the previous transition, even though the new dehydrated
257+ // state did not contain it
258+ rendered . getByText ( 'added' )
259+ } )
260+
261+ await sleep ( 10 )
262+ // It should stay the same even after effects have had a chance to run
263+ rendered . getByText ( 'string' )
264+ expect ( rendered . queryByText ( 'should not change' ) ) . toBeNull ( )
161265
162266 queryClient . clear ( )
163267 } )
0 commit comments