-
-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(solid-query): ssr fixes #5093
Changes from all commits
c909c0d
b9693b0
60a29db
8ac571f
ce95c26
2277853
e7f6c8a
59b505e
5c9a953
9a82950
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import type { CreateQueryResult } from '@tanstack/solid-query' | ||
import { createQuery } from '@tanstack/solid-query' | ||
import { createSignal, Suspense } from 'solid-js' | ||
import { fetchUser } from '~/utils/api' | ||
import { NoHydration } from 'solid-js/web' | ||
import { Title } from 'solid-start' | ||
|
||
export default function Hydration() { | ||
const query = createQuery(() => ({ | ||
queryKey: ['user'], | ||
queryFn: () => fetchUser({ sleep: 500 }), | ||
deferStream: true, | ||
})) | ||
|
||
const [initialQueryState] = createSignal(JSON.parse(JSON.stringify(query))) | ||
|
||
return ( | ||
<main> | ||
<Title>Solid Query - Hydration</Title> | ||
|
||
<h1>Solid Query - Hydration Example</h1> | ||
|
||
<div class="description"> | ||
<p> | ||
Lists the query state as seen on the server, initial render on the | ||
client (right after hydration), and current client value. Ideally, if | ||
SSR is setup correctly, these values are exactly the same in all three | ||
contexts. | ||
</p> | ||
</div> | ||
|
||
<button onClick={() => query.refetch()}>Refetch</button> | ||
|
||
<table class="example example--table"> | ||
<thead> | ||
<tr> | ||
<th>Context</th> | ||
<th>data.name</th> | ||
<th>isFetching</th> | ||
<th>isFetched</th> | ||
<th>isPending</th> | ||
<th>isRefetching</th> | ||
<th>isLoading</th> | ||
<th>isStale</th> | ||
<th>isSuccess</th> | ||
<th>isError</th> | ||
<th>error</th> | ||
<th>fetchStatus</th> | ||
<th>dataUpdatedAt</th> | ||
</tr> | ||
</thead> | ||
|
||
<tbody> | ||
<Suspense> | ||
<NoHydration> | ||
<QueryStateRow context="server" query={query} /> | ||
</NoHydration> | ||
|
||
<QueryStateRow | ||
context="client (initial render)" | ||
query={initialQueryState()!} | ||
/> | ||
|
||
<QueryStateRow context="client" query={query} /> | ||
</Suspense> | ||
</tbody> | ||
</table> | ||
</main> | ||
) | ||
} | ||
|
||
type QueryState = CreateQueryResult< | ||
{ | ||
id: string | ||
name: string | ||
queryTime: number | ||
}, | ||
Error | ||
> | ||
|
||
const QueryStateRow = (props: { context: string; query: QueryState }) => { | ||
return ( | ||
<tr> | ||
<td>{props.context}</td> | ||
<td>{props.query.data?.name}</td> | ||
<td>{String(props.query.isFetching)}</td> | ||
<td>{String(props.query.isFetched)}</td> | ||
<td>{String(props.query.isPending)}</td> | ||
<td>{String(props.query.isRefetching)}</td> | ||
<td>{String(props.query.isLoading)}</td> | ||
<td>{String(props.query.isStale)}</td> | ||
<td>{String(props.query.isSuccess)}</td> | ||
<td>{String(props.query.isError)}</td> | ||
<td>{String(props.query.error)}</td> | ||
<td>{String(props.query.fetchStatus)}</td> | ||
<td>{String(props.query.dataUpdatedAt)}</td> | ||
</tr> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,6 @@ import { | |
createResource, | ||
on, | ||
onCleanup, | ||
onMount, | ||
} from 'solid-js' | ||
import { createStore, unwrap } from 'solid-js/store' | ||
import { useQueryClient } from './QueryClientProvider' | ||
|
@@ -64,7 +63,20 @@ export function createBaseQuery< | |
) => { | ||
return observer.subscribe((result) => { | ||
notifyManager.batchCalls(() => { | ||
const unwrappedResult = { ...unwrap(result) } | ||
const query = observer.getCurrentQuery() | ||
const unwrappedResult = { | ||
...unwrap(result), | ||
|
||
// hydrate() expects a QueryState object, which is similar but not | ||
// quite the same as a QueryObserverResult object. Thus, for now, we're | ||
// copying over the missing properties from state in order to support hydration | ||
dataUpdateCount: query.state.dataUpdateCount, | ||
fetchFailureCount: query.state.fetchFailureCount, | ||
fetchFailureReason: query.state.fetchFailureReason, | ||
fetchMeta: query.state.fetchMeta, | ||
isInvalidated: query.state.isInvalidated, | ||
Comment on lines
+70
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does indeed fix the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the updates! Yeah this feels like a fine tradeoff for now! |
||
} | ||
|
||
if (unwrappedResult.isError) { | ||
if (process.env['NODE_ENV'] === 'development') { | ||
console.error(unwrappedResult.error) | ||
|
@@ -178,13 +190,17 @@ export function createBaseQuery< | |
} | ||
}) | ||
|
||
onMount(() => { | ||
observer.setOptions(defaultedOptions, { listeners: false }) | ||
}) | ||
Comment on lines
-181
to
-183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I can tell, this isn't doing anything. Removed to simplify and reduce the number of calls to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would have to test more but this is from the first iteration of creating the adapter. I dont think we would need for now |
||
|
||
createComputed(() => { | ||
observer.setOptions(client().defaultQueryOptions(options())) | ||
}) | ||
createComputed( | ||
on( | ||
() => client().defaultQueryOptions(options()), | ||
() => observer.setOptions(client().defaultQueryOptions(options())), | ||
{ | ||
// Defer because we don't need to trigger on first render | ||
// This only cares about changes to options after the observer is created | ||
defer: true, | ||
}, | ||
), | ||
) | ||
|
||
createComputed( | ||
on( | ||
|
@@ -209,10 +225,8 @@ export function createBaseQuery< | |
target: QueryObserverResult<TData, TError>, | ||
prop: keyof QueryObserverResult<TData, TError>, | ||
): any { | ||
if (prop === 'data') { | ||
return queryResource()?.data | ||
} | ||
return Reflect.get(target, prop) | ||
const val = queryResource()?.[prop] | ||
return val !== undefined ? val : Reflect.get(target, prop) | ||
}, | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bumped for this fix -> solidjs/solid@60f8624