Skip to content

Commit 3c9dc0f

Browse files
authored
feat: include meta in query and mutation hydration (#5733)
* feat: include `meta` in query and mutation hydration * test: tests for meta hydration * chore: stabilize a flaky test * chore: stabilize another flaky test
1 parent 69f59b1 commit 3c9dc0f

File tree

3 files changed

+131
-11
lines changed

3 files changed

+131
-11
lines changed

packages/query-core/src/hydration.ts

+8
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import type { QueryClient } from './queryClient'
22
import type { Query, QueryState } from './query'
33
import type {
44
MutationKey,
5+
MutationMeta,
56
MutationOptions,
67
QueryKey,
8+
QueryMeta,
79
QueryOptions,
810
} from './types'
911
import type { Mutation, MutationState } from './mutation'
@@ -25,12 +27,14 @@ export interface HydrateOptions {
2527
interface DehydratedMutation {
2628
mutationKey?: MutationKey
2729
state: MutationState
30+
meta?: MutationMeta
2831
}
2932

3033
interface DehydratedQuery {
3134
queryHash: string
3235
queryKey: QueryKey
3336
state: QueryState
37+
meta?: QueryMeta
3438
}
3539

3640
export interface DehydratedState {
@@ -44,6 +48,7 @@ function dehydrateMutation(mutation: Mutation): DehydratedMutation {
4448
return {
4549
mutationKey: mutation.options.mutationKey,
4650
state: mutation.state,
51+
...(mutation.meta && { meta: mutation.meta }),
4752
}
4853
}
4954

@@ -56,6 +61,7 @@ function dehydrateQuery(query: Query): DehydratedQuery {
5661
state: query.state,
5762
queryKey: query.queryKey,
5863
queryHash: query.queryHash,
64+
...(query.meta && { meta: query.meta }),
5965
}
6066
}
6167

@@ -115,6 +121,7 @@ export function hydrate(
115121
{
116122
...options?.defaultOptions?.mutations,
117123
mutationKey: dehydratedMutation.mutationKey,
124+
meta: dehydratedMutation.meta,
118125
},
119126
dehydratedMutation.state,
120127
)
@@ -145,6 +152,7 @@ export function hydrate(
145152
...options?.defaultOptions?.queries,
146153
queryKey: dehydratedQuery.queryKey,
147154
queryHash: dehydratedQuery.queryHash,
155+
meta: dehydratedQuery.meta,
148156
},
149157
dehydratedQueryState,
150158
)

packages/query-core/src/tests/hydration.test.tsx

+112-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { vi } from 'vitest'
1+
import { expect, vi } from 'vitest'
22
import { QueryCache } from '../queryCache'
33
import { dehydrate, hydrate } from '../hydration'
4+
import { MutationCache } from '../mutationCache'
45
import {
56
createQueryClient,
67
executeMutation,
@@ -557,4 +558,114 @@ describe('dehydration and rehydration', () => {
557558
hydrationCache.find({ queryKey: ['string'] })?.state.fetchStatus,
558559
).toBe('idle')
559560
})
561+
562+
test('should dehydrate and hydrate meta for queries', async () => {
563+
const queryCache = new QueryCache()
564+
const queryClient = createQueryClient({ queryCache })
565+
await queryClient.prefetchQuery({
566+
queryKey: ['meta'],
567+
queryFn: () => Promise.resolve('meta'),
568+
meta: {
569+
some: 'meta',
570+
},
571+
})
572+
await queryClient.prefetchQuery({
573+
queryKey: ['no-meta'],
574+
queryFn: () => Promise.resolve('no-meta'),
575+
})
576+
577+
const dehydrated = dehydrate(queryClient)
578+
579+
expect(
580+
dehydrated.queries.find((q) => q.queryHash === '["meta"]')?.meta,
581+
).toEqual({
582+
some: 'meta',
583+
})
584+
585+
expect(
586+
dehydrated.queries.find((q) => q.queryHash === '["no-meta"]')?.meta,
587+
).toEqual(undefined)
588+
589+
expect(
590+
Object.keys(
591+
dehydrated.queries.find((q) => q.queryHash === '["no-meta"]')!,
592+
),
593+
).not.toEqual(expect.arrayContaining(['meta']))
594+
595+
const stringified = JSON.stringify(dehydrated)
596+
597+
// ---
598+
599+
const parsed = JSON.parse(stringified)
600+
const hydrationCache = new QueryCache()
601+
const hydrationClient = createQueryClient({
602+
queryCache: hydrationCache,
603+
})
604+
hydrate(hydrationClient, parsed)
605+
expect(hydrationCache.find({ queryKey: ['meta'] })?.meta).toEqual({
606+
some: 'meta',
607+
})
608+
expect(hydrationCache.find({ queryKey: ['no-meta'] })?.meta).toEqual(
609+
undefined,
610+
)
611+
})
612+
613+
test('should dehydrate and hydrate meta for mutations', async () => {
614+
const mutationCache = new MutationCache()
615+
const queryClient = createQueryClient({ mutationCache })
616+
617+
await executeMutation(
618+
queryClient,
619+
{
620+
mutationKey: ['meta'],
621+
mutationFn: () => Promise.resolve('meta'),
622+
meta: {
623+
some: 'meta',
624+
},
625+
},
626+
undefined,
627+
)
628+
629+
await executeMutation(
630+
queryClient,
631+
{
632+
mutationKey: ['no-meta'],
633+
mutationFn: () => Promise.resolve('no-meta'),
634+
},
635+
undefined,
636+
)
637+
638+
const dehydrated = dehydrate(queryClient, {
639+
shouldDehydrateMutation: () => true,
640+
})
641+
642+
expect(Object.keys(dehydrated.mutations[0]!)).toEqual(
643+
expect.arrayContaining(['meta']),
644+
)
645+
expect(dehydrated.mutations[0]?.meta).toEqual({
646+
some: 'meta',
647+
})
648+
649+
expect(Object.keys(dehydrated.mutations[1]!)).not.toEqual(
650+
expect.arrayContaining(['meta']),
651+
)
652+
expect(dehydrated.mutations[1]?.meta).toEqual(undefined)
653+
654+
const stringified = JSON.stringify(dehydrated)
655+
656+
// ---
657+
658+
const parsed = JSON.parse(stringified)
659+
const hydrationCache = new MutationCache()
660+
const hydrationClient = createQueryClient({
661+
mutationCache: hydrationCache,
662+
})
663+
hydrate(hydrationClient, parsed)
664+
expect(hydrationCache.find({ mutationKey: ['meta'] })?.meta).toEqual({
665+
some: 'meta',
666+
})
667+
expect(hydrationCache.find({ mutationKey: ['no-meta'] })?.meta).toEqual(
668+
undefined,
669+
)
670+
})
560671
})

packages/react-query/src/__tests__/useQuery.test.tsx

+11-10
Original file line numberDiff line numberDiff line change
@@ -2603,24 +2603,24 @@ describe('useQuery', () => {
26032603
refetchOnWindowFocus: 'always',
26042604
})
26052605
states.push(state)
2606-
return null
2606+
return (
2607+
<div>
2608+
<div>
2609+
data: {state.data}, isFetching: {String(state.isFetching)}
2610+
</div>
2611+
</div>
2612+
)
26072613
}
26082614

2609-
renderWithClient(queryClient, <Page />)
2615+
const rendered = renderWithClient(queryClient, <Page />)
26102616

2611-
await sleep(20)
2617+
await waitFor(() => rendered.getByText('data: 0, isFetching: false'))
26122618

26132619
act(() => {
26142620
window.dispatchEvent(new Event('visibilitychange'))
26152621
})
26162622

2617-
await sleep(20)
2618-
2619-
await waitFor(() => expect(states.length).toBe(4))
2620-
expect(states[0]).toMatchObject({ data: undefined, isFetching: true })
2621-
expect(states[1]).toMatchObject({ data: 0, isFetching: false })
2622-
expect(states[2]).toMatchObject({ data: 0, isFetching: true })
2623-
expect(states[3]).toMatchObject({ data: 1, isFetching: false })
2623+
await waitFor(() => rendered.getByText('data: 1, isFetching: false'))
26242624
})
26252625

26262626
it('should calculate focus behaviour for `refetchOnWindowFocus` depending on function', async () => {
@@ -5458,6 +5458,7 @@ describe('useQuery', () => {
54585458
await waitFor(() => rendered.getByText('failureReason: failed1'))
54595459

54605460
const onlineMock = mockNavigatorOnLine(false)
5461+
window.dispatchEvent(new Event('offline'))
54615462

54625463
await sleep(20)
54635464

0 commit comments

Comments
 (0)