@tanstack/react-query 5.0.0-beta.27 → 5.0.0-beta.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/legacy/HydrationBoundary.cjs +37 -2
- package/build/legacy/HydrationBoundary.cjs.map +1 -1
- package/build/legacy/HydrationBoundary.d.cts +3 -1
- package/build/legacy/HydrationBoundary.d.ts +3 -1
- package/build/legacy/HydrationBoundary.js +37 -2
- package/build/legacy/HydrationBoundary.js.map +1 -1
- package/build/modern/HydrationBoundary.cjs +37 -2
- package/build/modern/HydrationBoundary.cjs.map +1 -1
- package/build/modern/HydrationBoundary.d.cts +3 -1
- package/build/modern/HydrationBoundary.d.ts +3 -1
- package/build/modern/HydrationBoundary.js +37 -2
- package/build/modern/HydrationBoundary.js.map +1 -1
- package/package.json +2 -2
- package/src/HydrationBoundary.tsx +78 -8
- package/src/__tests__/HydrationBoundary.test.tsx +111 -7
- package/src/__tests__/fine-grained-persister.test.tsx +163 -0
- package/build/codemods/coverage/base.css +0 -224
- package/build/codemods/coverage/block-navigation.js +0 -87
- package/build/codemods/coverage/clover.xml +0 -6
- package/build/codemods/coverage/coverage-final.json +0 -1
- package/build/codemods/coverage/favicon.png +0 -0
- package/build/codemods/coverage/index.html +0 -101
- package/build/codemods/coverage/prettify.css +0 -1
- package/build/codemods/coverage/prettify.js +0 -2
- package/build/codemods/coverage/sort-arrow-sprite.png +0 -0
- package/build/codemods/coverage/sorter.js +0 -196
|
@@ -44,13 +44,48 @@ var HydrationBoundary = ({
|
|
|
44
44
|
queryClient
|
|
45
45
|
}) => {
|
|
46
46
|
const client = (0, import_QueryClientProvider.useQueryClient)(queryClient);
|
|
47
|
+
const [hydrationQueue, setHydrationQueue] = React.useState();
|
|
47
48
|
const optionsRef = React.useRef(options);
|
|
48
49
|
optionsRef.current = options;
|
|
49
50
|
React.useMemo(() => {
|
|
50
51
|
if (state) {
|
|
51
|
-
|
|
52
|
+
if (typeof state !== "object") {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const queryCache = client.getQueryCache();
|
|
56
|
+
const queries = state.queries || [];
|
|
57
|
+
const newQueries = [];
|
|
58
|
+
const existingQueries = [];
|
|
59
|
+
for (const dehydratedQuery of queries) {
|
|
60
|
+
const existingQuery = queryCache.get(dehydratedQuery.queryHash);
|
|
61
|
+
if (!existingQuery) {
|
|
62
|
+
newQueries.push(dehydratedQuery);
|
|
63
|
+
} else {
|
|
64
|
+
const hydrationIsNewer = dehydratedQuery.state.dataUpdatedAt > existingQuery.state.dataUpdatedAt;
|
|
65
|
+
const queryAlreadyQueued = hydrationQueue == null ? void 0 : hydrationQueue.find(
|
|
66
|
+
(query) => query.queryHash === dehydratedQuery.queryHash
|
|
67
|
+
);
|
|
68
|
+
if (hydrationIsNewer && (!queryAlreadyQueued || dehydratedQuery.state.dataUpdatedAt > queryAlreadyQueued.state.dataUpdatedAt)) {
|
|
69
|
+
existingQueries.push(dehydratedQuery);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (newQueries.length > 0) {
|
|
74
|
+
(0, import_query_core.hydrate)(client, { queries: newQueries }, optionsRef.current);
|
|
75
|
+
}
|
|
76
|
+
if (existingQueries.length > 0) {
|
|
77
|
+
setHydrationQueue(
|
|
78
|
+
(prev) => prev ? [...prev, ...existingQueries] : existingQueries
|
|
79
|
+
);
|
|
80
|
+
}
|
|
52
81
|
}
|
|
53
|
-
}, [client, state]);
|
|
82
|
+
}, [client, hydrationQueue, state]);
|
|
83
|
+
React.useEffect(() => {
|
|
84
|
+
if (hydrationQueue) {
|
|
85
|
+
(0, import_query_core.hydrate)(client, { queries: hydrationQueue }, optionsRef.current);
|
|
86
|
+
setHydrationQueue(void 0);
|
|
87
|
+
}
|
|
88
|
+
}, [client, hydrationQueue]);
|
|
54
89
|
return children;
|
|
55
90
|
};
|
|
56
91
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\nimport * as React from 'react'\n\nimport { hydrate } from '@tanstack/query-core'\nimport { useQueryClient } from './QueryClientProvider'\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\nimport * as React from 'react'\n\nimport { hydrate } from '@tanstack/query-core'\nimport { useQueryClient } from './QueryClientProvider'\nimport type {\n DehydratedState,\n HydrateOptions,\n QueryClient,\n} from '@tanstack/query-core'\n\nexport interface HydrationBoundaryProps {\n state?: unknown\n options?: Omit<HydrateOptions, 'defaultOptions'> & {\n defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>\n }\n children?: React.ReactNode\n queryClient?: QueryClient\n}\n\nexport const HydrationBoundary = ({\n children,\n options = {},\n state,\n queryClient,\n}: HydrationBoundaryProps) => {\n const client = useQueryClient(queryClient)\n const [hydrationQueue, setHydrationQueue] = React.useState<\n DehydratedState['queries'] | undefined\n >()\n\n const optionsRef = React.useRef(options)\n optionsRef.current = options\n\n // This useMemo is for performance reasons only, everything inside it _must_\n // be safe to run in every render and code here should be read as \"in render\".\n //\n // This code needs to happen during the render phase, because after initial\n // SSR, hydration needs to happen _before_ children render. Also, if hydrating\n // during a transition, we want to hydrate as much as is safe in render so\n // we can prerender as much as possible.\n //\n // For any queries that already exist in the cache, we want to hold back on\n // hydrating until _after_ the render phase. The reason for this is that during\n // transitions, we don't want the existing queries and observers to update to\n // the new data on the current page, only _after_ the transition is committed.\n // If the transition is aborted, we will have hydrated any _new_ queries, but\n // we throw away the fresh data for any existing ones to avoid unexpectedly\n // updating the UI.\n React.useMemo(() => {\n if (state) {\n if (typeof state !== 'object') {\n return\n }\n\n const queryCache = client.getQueryCache()\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (state as DehydratedState).queries || []\n\n const newQueries: DehydratedState['queries'] = []\n const existingQueries: DehydratedState['queries'] = []\n for (const dehydratedQuery of queries) {\n const existingQuery = queryCache.get(dehydratedQuery.queryHash)\n\n if (!existingQuery) {\n newQueries.push(dehydratedQuery)\n } else {\n const hydrationIsNewer =\n dehydratedQuery.state.dataUpdatedAt >\n existingQuery.state.dataUpdatedAt\n const queryAlreadyQueued = hydrationQueue?.find(\n (query) => query.queryHash === dehydratedQuery.queryHash,\n )\n\n if (\n hydrationIsNewer &&\n (!queryAlreadyQueued ||\n dehydratedQuery.state.dataUpdatedAt >\n queryAlreadyQueued.state.dataUpdatedAt)\n ) {\n existingQueries.push(dehydratedQuery)\n }\n }\n }\n\n if (newQueries.length > 0) {\n // It's actually fine to call this with queries/state that already exists\n // in the cache, or is older. hydrate() is idempotent for queries.\n hydrate(client, { queries: newQueries }, optionsRef.current)\n }\n if (existingQueries.length > 0) {\n setHydrationQueue((prev) =>\n prev ? [...prev, ...existingQueries] : existingQueries,\n )\n }\n }\n }, [client, hydrationQueue, state])\n\n React.useEffect(() => {\n if (hydrationQueue) {\n hydrate(client, { queries: hydrationQueue }, optionsRef.current)\n setHydrationQueue(undefined)\n }\n }, [client, hydrationQueue])\n\n return children as React.ReactElement\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,YAAuB;AAEvB,wBAAwB;AACxB,iCAA+B;AAgBxB,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA,UAAU,CAAC;AAAA,EACX;AAAA,EACA;AACF,MAA8B;AAC5B,QAAM,aAAS,2CAAe,WAAW;AACzC,QAAM,CAAC,gBAAgB,iBAAiB,IAAU,eAEhD;AAEF,QAAM,aAAmB,aAAO,OAAO;AACvC,aAAW,UAAU;AAiBrB,EAAM,cAAQ,MAAM;AAClB,QAAI,OAAO;AACT,UAAI,OAAO,UAAU,UAAU;AAC7B;AAAA,MACF;AAEA,YAAM,aAAa,OAAO,cAAc;AAExC,YAAM,UAAW,MAA0B,WAAW,CAAC;AAEvD,YAAM,aAAyC,CAAC;AAChD,YAAM,kBAA8C,CAAC;AACrD,iBAAW,mBAAmB,SAAS;AACrC,cAAM,gBAAgB,WAAW,IAAI,gBAAgB,SAAS;AAE9D,YAAI,CAAC,eAAe;AAClB,qBAAW,KAAK,eAAe;AAAA,QACjC,OAAO;AACL,gBAAM,mBACJ,gBAAgB,MAAM,gBACtB,cAAc,MAAM;AACtB,gBAAM,qBAAqB,iDAAgB;AAAA,YACzC,CAAC,UAAU,MAAM,cAAc,gBAAgB;AAAA;AAGjD,cACE,qBACC,CAAC,sBACA,gBAAgB,MAAM,gBACpB,mBAAmB,MAAM,gBAC7B;AACA,4BAAgB,KAAK,eAAe;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AAGzB,uCAAQ,QAAQ,EAAE,SAAS,WAAW,GAAG,WAAW,OAAO;AAAA,MAC7D;AACA,UAAI,gBAAgB,SAAS,GAAG;AAC9B;AAAA,UAAkB,CAAC,SACjB,OAAO,CAAC,GAAG,MAAM,GAAG,eAAe,IAAI;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,gBAAgB,KAAK,CAAC;AAElC,EAAM,gBAAU,MAAM;AACpB,QAAI,gBAAgB;AAClB,qCAAQ,QAAQ,EAAE,SAAS,eAAe,GAAG,WAAW,OAAO;AAC/D,wBAAkB,MAAS;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,CAAC;AAE3B,SAAO;AACT;","names":[]}
|
|
@@ -3,7 +3,9 @@ import { HydrateOptions, QueryClient } from '@tanstack/query-core';
|
|
|
3
3
|
|
|
4
4
|
interface HydrationBoundaryProps {
|
|
5
5
|
state?: unknown;
|
|
6
|
-
options?: HydrateOptions
|
|
6
|
+
options?: Omit<HydrateOptions, 'defaultOptions'> & {
|
|
7
|
+
defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>;
|
|
8
|
+
};
|
|
7
9
|
children?: React.ReactNode;
|
|
8
10
|
queryClient?: QueryClient;
|
|
9
11
|
}
|
|
@@ -3,7 +3,9 @@ import { HydrateOptions, QueryClient } from '@tanstack/query-core';
|
|
|
3
3
|
|
|
4
4
|
interface HydrationBoundaryProps {
|
|
5
5
|
state?: unknown;
|
|
6
|
-
options?: HydrateOptions
|
|
6
|
+
options?: Omit<HydrateOptions, 'defaultOptions'> & {
|
|
7
|
+
defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>;
|
|
8
|
+
};
|
|
7
9
|
children?: React.ReactNode;
|
|
8
10
|
queryClient?: QueryClient;
|
|
9
11
|
}
|
|
@@ -11,13 +11,48 @@ var HydrationBoundary = ({
|
|
|
11
11
|
queryClient
|
|
12
12
|
}) => {
|
|
13
13
|
const client = useQueryClient(queryClient);
|
|
14
|
+
const [hydrationQueue, setHydrationQueue] = React.useState();
|
|
14
15
|
const optionsRef = React.useRef(options);
|
|
15
16
|
optionsRef.current = options;
|
|
16
17
|
React.useMemo(() => {
|
|
17
18
|
if (state) {
|
|
18
|
-
|
|
19
|
+
if (typeof state !== "object") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const queryCache = client.getQueryCache();
|
|
23
|
+
const queries = state.queries || [];
|
|
24
|
+
const newQueries = [];
|
|
25
|
+
const existingQueries = [];
|
|
26
|
+
for (const dehydratedQuery of queries) {
|
|
27
|
+
const existingQuery = queryCache.get(dehydratedQuery.queryHash);
|
|
28
|
+
if (!existingQuery) {
|
|
29
|
+
newQueries.push(dehydratedQuery);
|
|
30
|
+
} else {
|
|
31
|
+
const hydrationIsNewer = dehydratedQuery.state.dataUpdatedAt > existingQuery.state.dataUpdatedAt;
|
|
32
|
+
const queryAlreadyQueued = hydrationQueue == null ? void 0 : hydrationQueue.find(
|
|
33
|
+
(query) => query.queryHash === dehydratedQuery.queryHash
|
|
34
|
+
);
|
|
35
|
+
if (hydrationIsNewer && (!queryAlreadyQueued || dehydratedQuery.state.dataUpdatedAt > queryAlreadyQueued.state.dataUpdatedAt)) {
|
|
36
|
+
existingQueries.push(dehydratedQuery);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (newQueries.length > 0) {
|
|
41
|
+
hydrate(client, { queries: newQueries }, optionsRef.current);
|
|
42
|
+
}
|
|
43
|
+
if (existingQueries.length > 0) {
|
|
44
|
+
setHydrationQueue(
|
|
45
|
+
(prev) => prev ? [...prev, ...existingQueries] : existingQueries
|
|
46
|
+
);
|
|
47
|
+
}
|
|
19
48
|
}
|
|
20
|
-
}, [client, state]);
|
|
49
|
+
}, [client, hydrationQueue, state]);
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
if (hydrationQueue) {
|
|
52
|
+
hydrate(client, { queries: hydrationQueue }, optionsRef.current);
|
|
53
|
+
setHydrationQueue(void 0);
|
|
54
|
+
}
|
|
55
|
+
}, [client, hydrationQueue]);
|
|
21
56
|
return children;
|
|
22
57
|
};
|
|
23
58
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\nimport * as React from 'react'\n\nimport { hydrate } from '@tanstack/query-core'\nimport { useQueryClient } from './QueryClientProvider'\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\nimport * as React from 'react'\n\nimport { hydrate } from '@tanstack/query-core'\nimport { useQueryClient } from './QueryClientProvider'\nimport type {\n DehydratedState,\n HydrateOptions,\n QueryClient,\n} from '@tanstack/query-core'\n\nexport interface HydrationBoundaryProps {\n state?: unknown\n options?: Omit<HydrateOptions, 'defaultOptions'> & {\n defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>\n }\n children?: React.ReactNode\n queryClient?: QueryClient\n}\n\nexport const HydrationBoundary = ({\n children,\n options = {},\n state,\n queryClient,\n}: HydrationBoundaryProps) => {\n const client = useQueryClient(queryClient)\n const [hydrationQueue, setHydrationQueue] = React.useState<\n DehydratedState['queries'] | undefined\n >()\n\n const optionsRef = React.useRef(options)\n optionsRef.current = options\n\n // This useMemo is for performance reasons only, everything inside it _must_\n // be safe to run in every render and code here should be read as \"in render\".\n //\n // This code needs to happen during the render phase, because after initial\n // SSR, hydration needs to happen _before_ children render. Also, if hydrating\n // during a transition, we want to hydrate as much as is safe in render so\n // we can prerender as much as possible.\n //\n // For any queries that already exist in the cache, we want to hold back on\n // hydrating until _after_ the render phase. The reason for this is that during\n // transitions, we don't want the existing queries and observers to update to\n // the new data on the current page, only _after_ the transition is committed.\n // If the transition is aborted, we will have hydrated any _new_ queries, but\n // we throw away the fresh data for any existing ones to avoid unexpectedly\n // updating the UI.\n React.useMemo(() => {\n if (state) {\n if (typeof state !== 'object') {\n return\n }\n\n const queryCache = client.getQueryCache()\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (state as DehydratedState).queries || []\n\n const newQueries: DehydratedState['queries'] = []\n const existingQueries: DehydratedState['queries'] = []\n for (const dehydratedQuery of queries) {\n const existingQuery = queryCache.get(dehydratedQuery.queryHash)\n\n if (!existingQuery) {\n newQueries.push(dehydratedQuery)\n } else {\n const hydrationIsNewer =\n dehydratedQuery.state.dataUpdatedAt >\n existingQuery.state.dataUpdatedAt\n const queryAlreadyQueued = hydrationQueue?.find(\n (query) => query.queryHash === dehydratedQuery.queryHash,\n )\n\n if (\n hydrationIsNewer &&\n (!queryAlreadyQueued ||\n dehydratedQuery.state.dataUpdatedAt >\n queryAlreadyQueued.state.dataUpdatedAt)\n ) {\n existingQueries.push(dehydratedQuery)\n }\n }\n }\n\n if (newQueries.length > 0) {\n // It's actually fine to call this with queries/state that already exists\n // in the cache, or is older. hydrate() is idempotent for queries.\n hydrate(client, { queries: newQueries }, optionsRef.current)\n }\n if (existingQueries.length > 0) {\n setHydrationQueue((prev) =>\n prev ? [...prev, ...existingQueries] : existingQueries,\n )\n }\n }\n }, [client, hydrationQueue, state])\n\n React.useEffect(() => {\n if (hydrationQueue) {\n hydrate(client, { queries: hydrationQueue }, optionsRef.current)\n setHydrationQueue(undefined)\n }\n }, [client, hydrationQueue])\n\n return children as React.ReactElement\n}\n"],"mappings":";;;AACA,YAAY,WAAW;AAEvB,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAgBxB,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA,UAAU,CAAC;AAAA,EACX;AAAA,EACA;AACF,MAA8B;AAC5B,QAAM,SAAS,eAAe,WAAW;AACzC,QAAM,CAAC,gBAAgB,iBAAiB,IAAU,eAEhD;AAEF,QAAM,aAAmB,aAAO,OAAO;AACvC,aAAW,UAAU;AAiBrB,EAAM,cAAQ,MAAM;AAClB,QAAI,OAAO;AACT,UAAI,OAAO,UAAU,UAAU;AAC7B;AAAA,MACF;AAEA,YAAM,aAAa,OAAO,cAAc;AAExC,YAAM,UAAW,MAA0B,WAAW,CAAC;AAEvD,YAAM,aAAyC,CAAC;AAChD,YAAM,kBAA8C,CAAC;AACrD,iBAAW,mBAAmB,SAAS;AACrC,cAAM,gBAAgB,WAAW,IAAI,gBAAgB,SAAS;AAE9D,YAAI,CAAC,eAAe;AAClB,qBAAW,KAAK,eAAe;AAAA,QACjC,OAAO;AACL,gBAAM,mBACJ,gBAAgB,MAAM,gBACtB,cAAc,MAAM;AACtB,gBAAM,qBAAqB,iDAAgB;AAAA,YACzC,CAAC,UAAU,MAAM,cAAc,gBAAgB;AAAA;AAGjD,cACE,qBACC,CAAC,sBACA,gBAAgB,MAAM,gBACpB,mBAAmB,MAAM,gBAC7B;AACA,4BAAgB,KAAK,eAAe;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AAGzB,gBAAQ,QAAQ,EAAE,SAAS,WAAW,GAAG,WAAW,OAAO;AAAA,MAC7D;AACA,UAAI,gBAAgB,SAAS,GAAG;AAC9B;AAAA,UAAkB,CAAC,SACjB,OAAO,CAAC,GAAG,MAAM,GAAG,eAAe,IAAI;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,gBAAgB,KAAK,CAAC;AAElC,EAAM,gBAAU,MAAM;AACpB,QAAI,gBAAgB;AAClB,cAAQ,QAAQ,EAAE,SAAS,eAAe,GAAG,WAAW,OAAO;AAC/D,wBAAkB,MAAS;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,CAAC;AAE3B,SAAO;AACT;","names":[]}
|
|
@@ -44,13 +44,48 @@ var HydrationBoundary = ({
|
|
|
44
44
|
queryClient
|
|
45
45
|
}) => {
|
|
46
46
|
const client = (0, import_QueryClientProvider.useQueryClient)(queryClient);
|
|
47
|
+
const [hydrationQueue, setHydrationQueue] = React.useState();
|
|
47
48
|
const optionsRef = React.useRef(options);
|
|
48
49
|
optionsRef.current = options;
|
|
49
50
|
React.useMemo(() => {
|
|
50
51
|
if (state) {
|
|
51
|
-
|
|
52
|
+
if (typeof state !== "object") {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const queryCache = client.getQueryCache();
|
|
56
|
+
const queries = state.queries || [];
|
|
57
|
+
const newQueries = [];
|
|
58
|
+
const existingQueries = [];
|
|
59
|
+
for (const dehydratedQuery of queries) {
|
|
60
|
+
const existingQuery = queryCache.get(dehydratedQuery.queryHash);
|
|
61
|
+
if (!existingQuery) {
|
|
62
|
+
newQueries.push(dehydratedQuery);
|
|
63
|
+
} else {
|
|
64
|
+
const hydrationIsNewer = dehydratedQuery.state.dataUpdatedAt > existingQuery.state.dataUpdatedAt;
|
|
65
|
+
const queryAlreadyQueued = hydrationQueue?.find(
|
|
66
|
+
(query) => query.queryHash === dehydratedQuery.queryHash
|
|
67
|
+
);
|
|
68
|
+
if (hydrationIsNewer && (!queryAlreadyQueued || dehydratedQuery.state.dataUpdatedAt > queryAlreadyQueued.state.dataUpdatedAt)) {
|
|
69
|
+
existingQueries.push(dehydratedQuery);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (newQueries.length > 0) {
|
|
74
|
+
(0, import_query_core.hydrate)(client, { queries: newQueries }, optionsRef.current);
|
|
75
|
+
}
|
|
76
|
+
if (existingQueries.length > 0) {
|
|
77
|
+
setHydrationQueue(
|
|
78
|
+
(prev) => prev ? [...prev, ...existingQueries] : existingQueries
|
|
79
|
+
);
|
|
80
|
+
}
|
|
52
81
|
}
|
|
53
|
-
}, [client, state]);
|
|
82
|
+
}, [client, hydrationQueue, state]);
|
|
83
|
+
React.useEffect(() => {
|
|
84
|
+
if (hydrationQueue) {
|
|
85
|
+
(0, import_query_core.hydrate)(client, { queries: hydrationQueue }, optionsRef.current);
|
|
86
|
+
setHydrationQueue(void 0);
|
|
87
|
+
}
|
|
88
|
+
}, [client, hydrationQueue]);
|
|
54
89
|
return children;
|
|
55
90
|
};
|
|
56
91
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\nimport * as React from 'react'\n\nimport { hydrate } from '@tanstack/query-core'\nimport { useQueryClient } from './QueryClientProvider'\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\nimport * as React from 'react'\n\nimport { hydrate } from '@tanstack/query-core'\nimport { useQueryClient } from './QueryClientProvider'\nimport type {\n DehydratedState,\n HydrateOptions,\n QueryClient,\n} from '@tanstack/query-core'\n\nexport interface HydrationBoundaryProps {\n state?: unknown\n options?: Omit<HydrateOptions, 'defaultOptions'> & {\n defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>\n }\n children?: React.ReactNode\n queryClient?: QueryClient\n}\n\nexport const HydrationBoundary = ({\n children,\n options = {},\n state,\n queryClient,\n}: HydrationBoundaryProps) => {\n const client = useQueryClient(queryClient)\n const [hydrationQueue, setHydrationQueue] = React.useState<\n DehydratedState['queries'] | undefined\n >()\n\n const optionsRef = React.useRef(options)\n optionsRef.current = options\n\n // This useMemo is for performance reasons only, everything inside it _must_\n // be safe to run in every render and code here should be read as \"in render\".\n //\n // This code needs to happen during the render phase, because after initial\n // SSR, hydration needs to happen _before_ children render. Also, if hydrating\n // during a transition, we want to hydrate as much as is safe in render so\n // we can prerender as much as possible.\n //\n // For any queries that already exist in the cache, we want to hold back on\n // hydrating until _after_ the render phase. The reason for this is that during\n // transitions, we don't want the existing queries and observers to update to\n // the new data on the current page, only _after_ the transition is committed.\n // If the transition is aborted, we will have hydrated any _new_ queries, but\n // we throw away the fresh data for any existing ones to avoid unexpectedly\n // updating the UI.\n React.useMemo(() => {\n if (state) {\n if (typeof state !== 'object') {\n return\n }\n\n const queryCache = client.getQueryCache()\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (state as DehydratedState).queries || []\n\n const newQueries: DehydratedState['queries'] = []\n const existingQueries: DehydratedState['queries'] = []\n for (const dehydratedQuery of queries) {\n const existingQuery = queryCache.get(dehydratedQuery.queryHash)\n\n if (!existingQuery) {\n newQueries.push(dehydratedQuery)\n } else {\n const hydrationIsNewer =\n dehydratedQuery.state.dataUpdatedAt >\n existingQuery.state.dataUpdatedAt\n const queryAlreadyQueued = hydrationQueue?.find(\n (query) => query.queryHash === dehydratedQuery.queryHash,\n )\n\n if (\n hydrationIsNewer &&\n (!queryAlreadyQueued ||\n dehydratedQuery.state.dataUpdatedAt >\n queryAlreadyQueued.state.dataUpdatedAt)\n ) {\n existingQueries.push(dehydratedQuery)\n }\n }\n }\n\n if (newQueries.length > 0) {\n // It's actually fine to call this with queries/state that already exists\n // in the cache, or is older. hydrate() is idempotent for queries.\n hydrate(client, { queries: newQueries }, optionsRef.current)\n }\n if (existingQueries.length > 0) {\n setHydrationQueue((prev) =>\n prev ? [...prev, ...existingQueries] : existingQueries,\n )\n }\n }\n }, [client, hydrationQueue, state])\n\n React.useEffect(() => {\n if (hydrationQueue) {\n hydrate(client, { queries: hydrationQueue }, optionsRef.current)\n setHydrationQueue(undefined)\n }\n }, [client, hydrationQueue])\n\n return children as React.ReactElement\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,YAAuB;AAEvB,wBAAwB;AACxB,iCAA+B;AAgBxB,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA,UAAU,CAAC;AAAA,EACX;AAAA,EACA;AACF,MAA8B;AAC5B,QAAM,aAAS,2CAAe,WAAW;AACzC,QAAM,CAAC,gBAAgB,iBAAiB,IAAU,eAEhD;AAEF,QAAM,aAAmB,aAAO,OAAO;AACvC,aAAW,UAAU;AAiBrB,EAAM,cAAQ,MAAM;AAClB,QAAI,OAAO;AACT,UAAI,OAAO,UAAU,UAAU;AAC7B;AAAA,MACF;AAEA,YAAM,aAAa,OAAO,cAAc;AAExC,YAAM,UAAW,MAA0B,WAAW,CAAC;AAEvD,YAAM,aAAyC,CAAC;AAChD,YAAM,kBAA8C,CAAC;AACrD,iBAAW,mBAAmB,SAAS;AACrC,cAAM,gBAAgB,WAAW,IAAI,gBAAgB,SAAS;AAE9D,YAAI,CAAC,eAAe;AAClB,qBAAW,KAAK,eAAe;AAAA,QACjC,OAAO;AACL,gBAAM,mBACJ,gBAAgB,MAAM,gBACtB,cAAc,MAAM;AACtB,gBAAM,qBAAqB,gBAAgB;AAAA,YACzC,CAAC,UAAU,MAAM,cAAc,gBAAgB;AAAA,UACjD;AAEA,cACE,qBACC,CAAC,sBACA,gBAAgB,MAAM,gBACpB,mBAAmB,MAAM,gBAC7B;AACA,4BAAgB,KAAK,eAAe;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AAGzB,uCAAQ,QAAQ,EAAE,SAAS,WAAW,GAAG,WAAW,OAAO;AAAA,MAC7D;AACA,UAAI,gBAAgB,SAAS,GAAG;AAC9B;AAAA,UAAkB,CAAC,SACjB,OAAO,CAAC,GAAG,MAAM,GAAG,eAAe,IAAI;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,gBAAgB,KAAK,CAAC;AAElC,EAAM,gBAAU,MAAM;AACpB,QAAI,gBAAgB;AAClB,qCAAQ,QAAQ,EAAE,SAAS,eAAe,GAAG,WAAW,OAAO;AAC/D,wBAAkB,MAAS;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,CAAC;AAE3B,SAAO;AACT;","names":[]}
|
|
@@ -3,7 +3,9 @@ import { HydrateOptions, QueryClient } from '@tanstack/query-core';
|
|
|
3
3
|
|
|
4
4
|
interface HydrationBoundaryProps {
|
|
5
5
|
state?: unknown;
|
|
6
|
-
options?: HydrateOptions
|
|
6
|
+
options?: Omit<HydrateOptions, 'defaultOptions'> & {
|
|
7
|
+
defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>;
|
|
8
|
+
};
|
|
7
9
|
children?: React.ReactNode;
|
|
8
10
|
queryClient?: QueryClient;
|
|
9
11
|
}
|
|
@@ -3,7 +3,9 @@ import { HydrateOptions, QueryClient } from '@tanstack/query-core';
|
|
|
3
3
|
|
|
4
4
|
interface HydrationBoundaryProps {
|
|
5
5
|
state?: unknown;
|
|
6
|
-
options?: HydrateOptions
|
|
6
|
+
options?: Omit<HydrateOptions, 'defaultOptions'> & {
|
|
7
|
+
defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>;
|
|
8
|
+
};
|
|
7
9
|
children?: React.ReactNode;
|
|
8
10
|
queryClient?: QueryClient;
|
|
9
11
|
}
|
|
@@ -11,13 +11,48 @@ var HydrationBoundary = ({
|
|
|
11
11
|
queryClient
|
|
12
12
|
}) => {
|
|
13
13
|
const client = useQueryClient(queryClient);
|
|
14
|
+
const [hydrationQueue, setHydrationQueue] = React.useState();
|
|
14
15
|
const optionsRef = React.useRef(options);
|
|
15
16
|
optionsRef.current = options;
|
|
16
17
|
React.useMemo(() => {
|
|
17
18
|
if (state) {
|
|
18
|
-
|
|
19
|
+
if (typeof state !== "object") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const queryCache = client.getQueryCache();
|
|
23
|
+
const queries = state.queries || [];
|
|
24
|
+
const newQueries = [];
|
|
25
|
+
const existingQueries = [];
|
|
26
|
+
for (const dehydratedQuery of queries) {
|
|
27
|
+
const existingQuery = queryCache.get(dehydratedQuery.queryHash);
|
|
28
|
+
if (!existingQuery) {
|
|
29
|
+
newQueries.push(dehydratedQuery);
|
|
30
|
+
} else {
|
|
31
|
+
const hydrationIsNewer = dehydratedQuery.state.dataUpdatedAt > existingQuery.state.dataUpdatedAt;
|
|
32
|
+
const queryAlreadyQueued = hydrationQueue?.find(
|
|
33
|
+
(query) => query.queryHash === dehydratedQuery.queryHash
|
|
34
|
+
);
|
|
35
|
+
if (hydrationIsNewer && (!queryAlreadyQueued || dehydratedQuery.state.dataUpdatedAt > queryAlreadyQueued.state.dataUpdatedAt)) {
|
|
36
|
+
existingQueries.push(dehydratedQuery);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (newQueries.length > 0) {
|
|
41
|
+
hydrate(client, { queries: newQueries }, optionsRef.current);
|
|
42
|
+
}
|
|
43
|
+
if (existingQueries.length > 0) {
|
|
44
|
+
setHydrationQueue(
|
|
45
|
+
(prev) => prev ? [...prev, ...existingQueries] : existingQueries
|
|
46
|
+
);
|
|
47
|
+
}
|
|
19
48
|
}
|
|
20
|
-
}, [client, state]);
|
|
49
|
+
}, [client, hydrationQueue, state]);
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
if (hydrationQueue) {
|
|
52
|
+
hydrate(client, { queries: hydrationQueue }, optionsRef.current);
|
|
53
|
+
setHydrationQueue(void 0);
|
|
54
|
+
}
|
|
55
|
+
}, [client, hydrationQueue]);
|
|
21
56
|
return children;
|
|
22
57
|
};
|
|
23
58
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\nimport * as React from 'react'\n\nimport { hydrate } from '@tanstack/query-core'\nimport { useQueryClient } from './QueryClientProvider'\nimport type {
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationBoundary.tsx"],"sourcesContent":["'use client'\nimport * as React from 'react'\n\nimport { hydrate } from '@tanstack/query-core'\nimport { useQueryClient } from './QueryClientProvider'\nimport type {\n DehydratedState,\n HydrateOptions,\n QueryClient,\n} from '@tanstack/query-core'\n\nexport interface HydrationBoundaryProps {\n state?: unknown\n options?: Omit<HydrateOptions, 'defaultOptions'> & {\n defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>\n }\n children?: React.ReactNode\n queryClient?: QueryClient\n}\n\nexport const HydrationBoundary = ({\n children,\n options = {},\n state,\n queryClient,\n}: HydrationBoundaryProps) => {\n const client = useQueryClient(queryClient)\n const [hydrationQueue, setHydrationQueue] = React.useState<\n DehydratedState['queries'] | undefined\n >()\n\n const optionsRef = React.useRef(options)\n optionsRef.current = options\n\n // This useMemo is for performance reasons only, everything inside it _must_\n // be safe to run in every render and code here should be read as \"in render\".\n //\n // This code needs to happen during the render phase, because after initial\n // SSR, hydration needs to happen _before_ children render. Also, if hydrating\n // during a transition, we want to hydrate as much as is safe in render so\n // we can prerender as much as possible.\n //\n // For any queries that already exist in the cache, we want to hold back on\n // hydrating until _after_ the render phase. The reason for this is that during\n // transitions, we don't want the existing queries and observers to update to\n // the new data on the current page, only _after_ the transition is committed.\n // If the transition is aborted, we will have hydrated any _new_ queries, but\n // we throw away the fresh data for any existing ones to avoid unexpectedly\n // updating the UI.\n React.useMemo(() => {\n if (state) {\n if (typeof state !== 'object') {\n return\n }\n\n const queryCache = client.getQueryCache()\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const queries = (state as DehydratedState).queries || []\n\n const newQueries: DehydratedState['queries'] = []\n const existingQueries: DehydratedState['queries'] = []\n for (const dehydratedQuery of queries) {\n const existingQuery = queryCache.get(dehydratedQuery.queryHash)\n\n if (!existingQuery) {\n newQueries.push(dehydratedQuery)\n } else {\n const hydrationIsNewer =\n dehydratedQuery.state.dataUpdatedAt >\n existingQuery.state.dataUpdatedAt\n const queryAlreadyQueued = hydrationQueue?.find(\n (query) => query.queryHash === dehydratedQuery.queryHash,\n )\n\n if (\n hydrationIsNewer &&\n (!queryAlreadyQueued ||\n dehydratedQuery.state.dataUpdatedAt >\n queryAlreadyQueued.state.dataUpdatedAt)\n ) {\n existingQueries.push(dehydratedQuery)\n }\n }\n }\n\n if (newQueries.length > 0) {\n // It's actually fine to call this with queries/state that already exists\n // in the cache, or is older. hydrate() is idempotent for queries.\n hydrate(client, { queries: newQueries }, optionsRef.current)\n }\n if (existingQueries.length > 0) {\n setHydrationQueue((prev) =>\n prev ? [...prev, ...existingQueries] : existingQueries,\n )\n }\n }\n }, [client, hydrationQueue, state])\n\n React.useEffect(() => {\n if (hydrationQueue) {\n hydrate(client, { queries: hydrationQueue }, optionsRef.current)\n setHydrationQueue(undefined)\n }\n }, [client, hydrationQueue])\n\n return children as React.ReactElement\n}\n"],"mappings":";;;AACA,YAAY,WAAW;AAEvB,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAgBxB,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA,UAAU,CAAC;AAAA,EACX;AAAA,EACA;AACF,MAA8B;AAC5B,QAAM,SAAS,eAAe,WAAW;AACzC,QAAM,CAAC,gBAAgB,iBAAiB,IAAU,eAEhD;AAEF,QAAM,aAAmB,aAAO,OAAO;AACvC,aAAW,UAAU;AAiBrB,EAAM,cAAQ,MAAM;AAClB,QAAI,OAAO;AACT,UAAI,OAAO,UAAU,UAAU;AAC7B;AAAA,MACF;AAEA,YAAM,aAAa,OAAO,cAAc;AAExC,YAAM,UAAW,MAA0B,WAAW,CAAC;AAEvD,YAAM,aAAyC,CAAC;AAChD,YAAM,kBAA8C,CAAC;AACrD,iBAAW,mBAAmB,SAAS;AACrC,cAAM,gBAAgB,WAAW,IAAI,gBAAgB,SAAS;AAE9D,YAAI,CAAC,eAAe;AAClB,qBAAW,KAAK,eAAe;AAAA,QACjC,OAAO;AACL,gBAAM,mBACJ,gBAAgB,MAAM,gBACtB,cAAc,MAAM;AACtB,gBAAM,qBAAqB,gBAAgB;AAAA,YACzC,CAAC,UAAU,MAAM,cAAc,gBAAgB;AAAA,UACjD;AAEA,cACE,qBACC,CAAC,sBACA,gBAAgB,MAAM,gBACpB,mBAAmB,MAAM,gBAC7B;AACA,4BAAgB,KAAK,eAAe;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,GAAG;AAGzB,gBAAQ,QAAQ,EAAE,SAAS,WAAW,GAAG,WAAW,OAAO;AAAA,MAC7D;AACA,UAAI,gBAAgB,SAAS,GAAG;AAC9B;AAAA,UAAkB,CAAC,SACjB,OAAO,CAAC,GAAG,MAAM,GAAG,eAAe,IAAI;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,gBAAgB,KAAK,CAAC;AAElC,EAAM,gBAAU,MAAM;AACpB,QAAI,gBAAgB;AAClB,cAAQ,QAAQ,EAAE,SAAS,eAAe,GAAG,WAAW,OAAO;AAC/D,wBAAkB,MAAS;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,CAAC;AAE3B,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-query",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.28",
|
|
4
4
|
"description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"client-only": "0.0.1",
|
|
45
|
-
"@tanstack/query-core": "5.0.0-beta.
|
|
45
|
+
"@tanstack/query-core": "5.0.0-beta.28"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/react": "^18.2.4",
|
|
@@ -3,11 +3,17 @@ import * as React from 'react'
|
|
|
3
3
|
|
|
4
4
|
import { hydrate } from '@tanstack/query-core'
|
|
5
5
|
import { useQueryClient } from './QueryClientProvider'
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
DehydratedState,
|
|
8
|
+
HydrateOptions,
|
|
9
|
+
QueryClient,
|
|
10
|
+
} from '@tanstack/query-core'
|
|
7
11
|
|
|
8
12
|
export interface HydrationBoundaryProps {
|
|
9
13
|
state?: unknown
|
|
10
|
-
options?: HydrateOptions
|
|
14
|
+
options?: Omit<HydrateOptions, 'defaultOptions'> & {
|
|
15
|
+
defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>
|
|
16
|
+
}
|
|
11
17
|
children?: React.ReactNode
|
|
12
18
|
queryClient?: QueryClient
|
|
13
19
|
}
|
|
@@ -19,19 +25,83 @@ export const HydrationBoundary = ({
|
|
|
19
25
|
queryClient,
|
|
20
26
|
}: HydrationBoundaryProps) => {
|
|
21
27
|
const client = useQueryClient(queryClient)
|
|
28
|
+
const [hydrationQueue, setHydrationQueue] = React.useState<
|
|
29
|
+
DehydratedState['queries'] | undefined
|
|
30
|
+
>()
|
|
22
31
|
|
|
23
32
|
const optionsRef = React.useRef(options)
|
|
24
33
|
optionsRef.current = options
|
|
25
34
|
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
35
|
+
// This useMemo is for performance reasons only, everything inside it _must_
|
|
36
|
+
// be safe to run in every render and code here should be read as "in render".
|
|
37
|
+
//
|
|
38
|
+
// This code needs to happen during the render phase, because after initial
|
|
39
|
+
// SSR, hydration needs to happen _before_ children render. Also, if hydrating
|
|
40
|
+
// during a transition, we want to hydrate as much as is safe in render so
|
|
41
|
+
// we can prerender as much as possible.
|
|
42
|
+
//
|
|
43
|
+
// For any queries that already exist in the cache, we want to hold back on
|
|
44
|
+
// hydrating until _after_ the render phase. The reason for this is that during
|
|
45
|
+
// transitions, we don't want the existing queries and observers to update to
|
|
46
|
+
// the new data on the current page, only _after_ the transition is committed.
|
|
47
|
+
// If the transition is aborted, we will have hydrated any _new_ queries, but
|
|
48
|
+
// we throw away the fresh data for any existing ones to avoid unexpectedly
|
|
49
|
+
// updating the UI.
|
|
30
50
|
React.useMemo(() => {
|
|
31
51
|
if (state) {
|
|
32
|
-
|
|
52
|
+
if (typeof state !== 'object') {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const queryCache = client.getQueryCache()
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
58
|
+
const queries = (state as DehydratedState).queries || []
|
|
59
|
+
|
|
60
|
+
const newQueries: DehydratedState['queries'] = []
|
|
61
|
+
const existingQueries: DehydratedState['queries'] = []
|
|
62
|
+
for (const dehydratedQuery of queries) {
|
|
63
|
+
const existingQuery = queryCache.get(dehydratedQuery.queryHash)
|
|
64
|
+
|
|
65
|
+
if (!existingQuery) {
|
|
66
|
+
newQueries.push(dehydratedQuery)
|
|
67
|
+
} else {
|
|
68
|
+
const hydrationIsNewer =
|
|
69
|
+
dehydratedQuery.state.dataUpdatedAt >
|
|
70
|
+
existingQuery.state.dataUpdatedAt
|
|
71
|
+
const queryAlreadyQueued = hydrationQueue?.find(
|
|
72
|
+
(query) => query.queryHash === dehydratedQuery.queryHash,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
hydrationIsNewer &&
|
|
77
|
+
(!queryAlreadyQueued ||
|
|
78
|
+
dehydratedQuery.state.dataUpdatedAt >
|
|
79
|
+
queryAlreadyQueued.state.dataUpdatedAt)
|
|
80
|
+
) {
|
|
81
|
+
existingQueries.push(dehydratedQuery)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (newQueries.length > 0) {
|
|
87
|
+
// It's actually fine to call this with queries/state that already exists
|
|
88
|
+
// in the cache, or is older. hydrate() is idempotent for queries.
|
|
89
|
+
hydrate(client, { queries: newQueries }, optionsRef.current)
|
|
90
|
+
}
|
|
91
|
+
if (existingQueries.length > 0) {
|
|
92
|
+
setHydrationQueue((prev) =>
|
|
93
|
+
prev ? [...prev, ...existingQueries] : existingQueries,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}, [client, hydrationQueue, state])
|
|
98
|
+
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
if (hydrationQueue) {
|
|
101
|
+
hydrate(client, { queries: hydrationQueue }, optionsRef.current)
|
|
102
|
+
setHydrationQueue(undefined)
|
|
33
103
|
}
|
|
34
|
-
}, [client,
|
|
104
|
+
}, [client, hydrationQueue])
|
|
35
105
|
|
|
36
106
|
return children as React.ReactElement
|
|
37
107
|
}
|
|
@@ -137,8 +137,8 @@ describe('React hydration', () => {
|
|
|
137
137
|
queryFn: () => dataQuery(['should change']),
|
|
138
138
|
})
|
|
139
139
|
await intermediateClient.prefetchQuery({
|
|
140
|
-
queryKey: ['added
|
|
141
|
-
queryFn: () => dataQuery(['added
|
|
140
|
+
queryKey: ['added'],
|
|
141
|
+
queryFn: () => dataQuery(['added']),
|
|
142
142
|
})
|
|
143
143
|
const dehydrated = dehydrate(intermediateClient)
|
|
144
144
|
intermediateClient.clear()
|
|
@@ -147,17 +147,121 @@ describe('React hydration', () => {
|
|
|
147
147
|
<QueryClientProvider client={queryClient}>
|
|
148
148
|
<HydrationBoundary state={dehydrated}>
|
|
149
149
|
<Page queryKey={['string']} />
|
|
150
|
-
<Page queryKey={['added
|
|
150
|
+
<Page queryKey={['added']} />
|
|
151
151
|
</HydrationBoundary>
|
|
152
152
|
</QueryClientProvider>,
|
|
153
153
|
)
|
|
154
154
|
|
|
155
|
-
// Existing
|
|
156
|
-
//
|
|
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
|
+
|
|
157
161
|
await sleep(10)
|
|
162
|
+
// After effects phase has had time to run, the observer should have updated
|
|
163
|
+
expect(rendered.queryByText('string')).toBeNull()
|
|
158
164
|
rendered.getByText('should change')
|
|
159
|
-
|
|
160
|
-
|
|
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()
|
|
161
265
|
|
|
162
266
|
queryClient.clear()
|
|
163
267
|
})
|