Skip to content

Commit 635cbf7

Browse files
author
Marc MacLeod
authored
fix(solid-query): SSR fixes - Inconsistent state (#5093)
* fix(solid-query): do not call unsubscribe on the server * fix(solid-query): prefer val from queryResource if it is available there * chore: add hydration debugger to solid ssr example * bump minimum solid-js version * chore: pnpm i * chore: fix rollup babel typings Result of this change upstream that types the “targets” property -> https://github.com/DefinitelyTyped/DefinitelyTyped/pull/63927/files#diff-a4da813bfdc10bbb7d4d9ee9b811309e7e2a16c0484b9a334752db84be4b0f31 Signed-off-by: marbemac <[email protected]> * replace void 0 Signed-off-by: marbemac <[email protected]> * chore: remove uncessary onMount Signed-off-by: marbemac <[email protected]> * chore: skip this onComputed on first render Signed-off-by: marbemac <[email protected]> * fix: copy state properties over for ssr hydration Signed-off-by: marbemac <[email protected]> --------- Signed-off-by: marbemac <[email protected]>
1 parent c39bf62 commit 635cbf7

File tree

13 files changed

+631
-172
lines changed

13 files changed

+631
-172
lines changed

examples/solid/basic-graphql-request/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@tanstack/solid-query": "^4.3.9",
1414
"graphql": "^16.6.0",
1515
"graphql-request": "^5.0.0",
16-
"solid-js": "^1.6.2"
16+
"solid-js": "^1.6.13"
1717
},
1818
"devDependencies": {
1919
"typescript": "4.7.4",

examples/solid/basic-typescript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"license": "MIT",
1212
"dependencies": {
1313
"@tanstack/solid-query": "^4.3.9",
14-
"solid-js": "^1.6.2"
14+
"solid-js": "^1.6.13"
1515
},
1616
"devDependencies": {
1717
"typescript": "4.7.4",

examples/solid/default-query-function/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"license": "MIT",
1212
"dependencies": {
1313
"@tanstack/solid-query": "^4.3.9",
14-
"solid-js": "^1.6.2"
14+
"solid-js": "^1.6.13"
1515
},
1616
"devDependencies": {
1717
"typescript": "4.7.4",

examples/solid/simple/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"license": "MIT",
1212
"dependencies": {
1313
"@tanstack/solid-query": "^4.3.9",
14-
"solid-js": "^1.6.2"
14+
"solid-js": "^1.6.13"
1515
},
1616
"devDependencies": {
1717
"@tanstack/eslint-plugin-query": "^4.13.0",

examples/solid/solid-start-streaming/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
"esbuild": "^0.14.54",
1212
"postcss": "^8.4.18",
1313
"solid-start-node": "^0.2.0",
14-
"typescript": "^4.8.4",
15-
"vite": "^3.1.8"
14+
"typescript": "^4.9.4",
15+
"vite": "^4.1.4"
1616
},
1717
"dependencies": {
18-
"@tanstack/solid-query": "^4.3.9",
18+
"@tanstack/solid-query": "^5.0.0-alpha.3",
1919
"@solidjs/meta": "^0.28.2",
20-
"@solidjs/router": "^0.6.0",
21-
"solid-js": "^1.6.9",
22-
"solid-start": "^0.2.15",
20+
"@solidjs/router": "^0.7.0",
21+
"solid-js": "^1.6.13",
22+
"solid-start": "^0.2.23",
2323
"undici": "^5.11.0"
2424
},
2525
"engines": {

examples/solid/solid-start-streaming/src/root.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,13 @@ p {
8585
font-weight: bold;
8686
flex-grow: 1;
8787
}
88+
89+
.example--table {
90+
padding: 0.5rem;
91+
}
92+
93+
.example--table th,
94+
.example--table td {
95+
padding: 4px 12px;
96+
white-space: nowrap;
97+
}

examples/solid/solid-start-streaming/src/root.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function Root() {
2020
const queryClient = new QueryClient({
2121
defaultOptions: {
2222
queries: {
23-
retry: 0,
23+
retry: false,
2424
},
2525
},
2626
})
@@ -43,6 +43,7 @@ export default function Root() {
4343
<A href="/deferred">Deferred</A>
4444
<A href="/mixed">Mixed</A>
4545
<A href="/with-error">With Error</A>
46+
<A href="/hydration">Hydration</A>
4647

4748
<Routes>
4849
<FileRoutes />
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import type { CreateQueryResult } from '@tanstack/solid-query'
2+
import { createQuery } from '@tanstack/solid-query'
3+
import { createSignal, Suspense } from 'solid-js'
4+
import { fetchUser } from '~/utils/api'
5+
import { NoHydration } from 'solid-js/web'
6+
import { Title } from 'solid-start'
7+
8+
export default function Hydration() {
9+
const query = createQuery(() => ({
10+
queryKey: ['user'],
11+
queryFn: () => fetchUser({ sleep: 500 }),
12+
deferStream: true,
13+
}))
14+
15+
const [initialQueryState] = createSignal(JSON.parse(JSON.stringify(query)))
16+
17+
return (
18+
<main>
19+
<Title>Solid Query - Hydration</Title>
20+
21+
<h1>Solid Query - Hydration Example</h1>
22+
23+
<div class="description">
24+
<p>
25+
Lists the query state as seen on the server, initial render on the
26+
client (right after hydration), and current client value. Ideally, if
27+
SSR is setup correctly, these values are exactly the same in all three
28+
contexts.
29+
</p>
30+
</div>
31+
32+
<button onClick={() => query.refetch()}>Refetch</button>
33+
34+
<table class="example example--table">
35+
<thead>
36+
<tr>
37+
<th>Context</th>
38+
<th>data.name</th>
39+
<th>isFetching</th>
40+
<th>isFetched</th>
41+
<th>isPending</th>
42+
<th>isRefetching</th>
43+
<th>isLoading</th>
44+
<th>isStale</th>
45+
<th>isSuccess</th>
46+
<th>isError</th>
47+
<th>error</th>
48+
<th>fetchStatus</th>
49+
<th>dataUpdatedAt</th>
50+
</tr>
51+
</thead>
52+
53+
<tbody>
54+
<Suspense>
55+
<NoHydration>
56+
<QueryStateRow context="server" query={query} />
57+
</NoHydration>
58+
59+
<QueryStateRow
60+
context="client (initial render)"
61+
query={initialQueryState()!}
62+
/>
63+
64+
<QueryStateRow context="client" query={query} />
65+
</Suspense>
66+
</tbody>
67+
</table>
68+
</main>
69+
)
70+
}
71+
72+
type QueryState = CreateQueryResult<
73+
{
74+
id: string
75+
name: string
76+
queryTime: number
77+
},
78+
Error
79+
>
80+
81+
const QueryStateRow = (props: { context: string; query: QueryState }) => {
82+
return (
83+
<tr>
84+
<td>{props.context}</td>
85+
<td>{props.query.data?.name}</td>
86+
<td>{String(props.query.isFetching)}</td>
87+
<td>{String(props.query.isFetched)}</td>
88+
<td>{String(props.query.isPending)}</td>
89+
<td>{String(props.query.isRefetching)}</td>
90+
<td>{String(props.query.isLoading)}</td>
91+
<td>{String(props.query.isStale)}</td>
92+
<td>{String(props.query.isSuccess)}</td>
93+
<td>{String(props.query.isError)}</td>
94+
<td>{String(props.query.error)}</td>
95+
<td>{String(props.query.fetchStatus)}</td>
96+
<td>{String(props.query.dataUpdatedAt)}</td>
97+
</tr>
98+
)
99+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
"rollup-plugin-visualizer": "^5.9.0",
9393
"rollup-preset-solid": "^1.4.0",
9494
"semver": "^7.3.8",
95-
"solid-js": "^1.5.7",
95+
"solid-js": "^1.6.13",
9696
"solid-testing-library": "^0.3.0",
9797
"stream-to-array": "^2.3.0",
9898
"ts-node": "^10.9.1",

packages/solid-query/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"@tanstack/query-core": "workspace:*"
5555
},
5656
"peerDependencies": {
57-
"solid-js": "^1.6.2"
57+
"solid-js": "^1.6.13"
5858
},
5959
"peerDependenciesMeta": {}
6060
}

packages/solid-query/src/createBaseQuery.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
createResource,
1919
on,
2020
onCleanup,
21-
onMount,
2221
} from 'solid-js'
2322
import { createStore, unwrap } from 'solid-js/store'
2423
import { useQueryClient } from './QueryClientProvider'
@@ -64,7 +63,20 @@ export function createBaseQuery<
6463
) => {
6564
return observer.subscribe((result) => {
6665
notifyManager.batchCalls(() => {
67-
const unwrappedResult = { ...unwrap(result) }
66+
const query = observer.getCurrentQuery()
67+
const unwrappedResult = {
68+
...unwrap(result),
69+
70+
// hydrate() expects a QueryState object, which is similar but not
71+
// quite the same as a QueryObserverResult object. Thus, for now, we're
72+
// copying over the missing properties from state in order to support hydration
73+
dataUpdateCount: query.state.dataUpdateCount,
74+
fetchFailureCount: query.state.fetchFailureCount,
75+
fetchFailureReason: query.state.fetchFailureReason,
76+
fetchMeta: query.state.fetchMeta,
77+
isInvalidated: query.state.isInvalidated,
78+
}
79+
6880
if (unwrappedResult.isError) {
6981
if (process.env['NODE_ENV'] === 'development') {
7082
console.error(unwrappedResult.error)
@@ -178,13 +190,17 @@ export function createBaseQuery<
178190
}
179191
})
180192

181-
onMount(() => {
182-
observer.setOptions(defaultedOptions, { listeners: false })
183-
})
184-
185-
createComputed(() => {
186-
observer.setOptions(client().defaultQueryOptions(options()))
187-
})
193+
createComputed(
194+
on(
195+
() => client().defaultQueryOptions(options()),
196+
() => observer.setOptions(client().defaultQueryOptions(options())),
197+
{
198+
// Defer because we don't need to trigger on first render
199+
// This only cares about changes to options after the observer is created
200+
defer: true,
201+
},
202+
),
203+
)
188204

189205
createComputed(
190206
on(
@@ -209,10 +225,8 @@ export function createBaseQuery<
209225
target: QueryObserverResult<TData, TError>,
210226
prop: keyof QueryObserverResult<TData, TError>,
211227
): any {
212-
if (prop === 'data') {
213-
return queryResource()?.data
214-
}
215-
return Reflect.get(target, prop)
228+
const val = queryResource()?.[prop]
229+
return val !== undefined ? val : Reflect.get(target, prop)
216230
},
217231
}
218232

0 commit comments

Comments
 (0)