-
Notifications
You must be signed in to change notification settings - Fork 142
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
[Feature] Integrate react-query
with PWA-Kit SDK
#693
Conversation
}) | ||
: Promise.resolve({}) | ||
) | ||
const queries = queryClient.queryCache.queries.map((q) => q.fetch()) |
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.
what happens when the query throw error? Is react-query able to hydrate the error?
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.
There are some limitations with serialization of errors. I can play around with it to see the actual behaviour, because it's not completely clear what happens to errors if you tell dehydrate to serialize them. Since it says you can't do that.
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.
I see. From the documentation, it looks like react-query does not provide a serializer for errors.
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.
It would be likely that the error would be handled one of a handful of ways that don't result in the need for serializing the error.
- The error is thrown and our server pipeline will do the appropriate things with it, e.g. set the status code, render the error page.
- The error is not thrown and handled on the page in which the page would not render an appropriate UI.
I still want to see what would happen is a query throws an error internally in its query function, then on the client we wouldn't re-run that query. I'm curious as to what data and or error are set to.
|
||
return ( | ||
<ExpressContext.Provider value={{req, res}}> | ||
<QueryClientProvider client={queryClient}> |
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.
<QueryClientProvider client={queryClient}> | |
<QueryClientProvider client={queryClient} contextSharing={true}> |
In response to @kevinxh 's question on Slack recently: I think both pwa-kit-react-sdk
and commerce-sdk-react
packages can mount their own QueryClientProvider, that is, if commerce-sdk-react
is meant to be a standalone library. By setting contextSharing
to true, it looks like it's ok to have multiple QueryClientProvider in the component tree, and only one instance will be used.
What contextSharing
option does: https://tanstack.com/query/v4/docs/reference/QueryClientProvider
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.
commerce-sdk-react
isn't going to be mounting it's own provider. That being said, it doesn't mean that someone wouldn't add their own for some weird reason. The question is, do we want to make that decision for the user.. also does context only get shared amongst providers that have that value set to true? hmm
…lesforceCommerceCloud/pwa-kit into react-query-ssr-investigation
this would waste space in the html.
- Make useQuery example in ts minimal server side only
routes | ||
}) | ||
|
||
;({appState, error: appStateError} = await WrappedApp.fetchState({ |
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.
Dumb question, why do we need to wrap this line around ;( ... )
?
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.
This line isn't in the code anymore, but for education purposes. I found out that this is how you accomplish using let to declare variables and have them be assigned to during destructuring.
The reason I was having to do this is because appState
and appStateError
were declared outside of this conditional because they are being used elsewhere. Alternatively you could use an intermediate variable like this
const result = await WrappedApp.fetchState(...)
appState = result.appState
appStateError = result.appStateError
@bendvc Follow-up to our earlier conversation on Slack: yup, there is a bug that can be seen by loading the retail-react-app-template, specifically with how In the retail-react-app:
|
{} | ||
) | ||
} catch (e) { | ||
appStateError = logAndFormatError(e || new Error()) |
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.
Querying Commerce API for a non-existent category, for example, would throw an error, which would get caught in this catch statement. The end result is this internal server error:
But what if the developers would want to catch and handle it in their app? Currently they can't because it's caught in the react rendering.
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.
One scenario I'm investigating is for the useExpress hook: how to detect a non-existent category and spit out a 404. I would want to be able to do the following in the user land:
const {categoryId} = useParams()
const {data, isLoading, error} = useCategory({id: categoryId, levels: 0})
const {req, res: pageRes} = useExpress()
if (pageRes && error?.response?.status === 404) {
pageRes.status(404).send(data?.detail)
}
We were able to do a similar thing in getProps
:
pwa-kit/packages/template-retail-react-app/app/pages/product-list/index.jsx
Lines 599 to 603 in 59a165f
// The `isomorphic-sdk` returns error objects when they occur, so we | |
// need to check the category type and throw if required. | |
if (category?.type?.endsWith('category-not-found')) { | |
throw new HTTPNotFound(category.detail) | |
} |
const queries = queryCache.getAll() | ||
const promises = queries | ||
.filter(({options}) => options.enabled !== false) | ||
.map((query) => query.fetch().catch((e) => ({error: e}))) |
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.
In case users would want to use the other error-related properties that are returned by useQuery
:
.map((query) => query.fetch().catch((e) => ({error: e}))) | |
.map((query) => query.fetch().catch((e) => ({error: e, status: 'error', isError: true}))) |
return { | ||
error, | ||
errorUpdatedAt: Date.now(), | ||
errorUpdateCount: 1, | ||
isError: true, | ||
isLoadingError: true, | ||
isRefetchError: false, | ||
status: 'error' | ||
} |
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.
I found that this object is not what I see on the user land. The object has more things in it.. more complete.
Is this because although we're returning all promises on line 82 below, what gets saved is actually the queryClient
itself on line 84?
Description
The objective of this PR is to integrate React Query with PWA-Kit, with the primary intention to allow use to build a hooks library that is compatible with our server-side rendering. Another motivating factor is to provide an alternative, not a replacement, to the aging
getProps
interface.This PR takes advantage of a library called
react-ssr-prepass
to record all usages of react-query in the "to be rendered" application so we can resolve those query requests, serialize the returned data, and continue on with the rending flow.The solution in this PR diverges slightly from the solution provided by @olibrook, in that we take special care to not serialize the awaiting of
getProps
and andreact-query
queries. In order to accomplish this I had to reorganize some of the code so that the prepass stage happens before the "appState" is initialized.During the implementation I ran into a couple things I'd like some input on:
prepass
stage have been moved outside of the renderApp method, if it was to throw, we would have an uncaught error. Although I did some tests to get the prepass stage to fail, it seems like because it only traverses the JSX elements it doesn't throw errors itself if a component was to throw in its render. I'm guessing it's probably safer to ensure we catch errors no matter what and pass them along to the renderApp method.<Hydrate >
component so that or options on the queryClient, to stop hydrated queries from being called again. But I haven't found what would be an acceptable solution, aside from setting thestaleTime
to a value that would cause the query to not be stale at the type of hydration. But that seems a little flaky. It would be nice to find a better solution for that._NOTE 1: The server side resolution of
react-query
queries is an opt-in feature, as the prepass state for rendering take proportional time to how long it takes to render. _NOTE 2: Support for dependent queries as described here is currently NOT supported for server side rendering. Queries that are not enabled will not be run on the server.
TODO's
getProps
we need to have access to the req/res objects. I'm thinking about creating a provider that will have the express request context. Maybe the name would be something likeuseExpressCallbackArgs
. I'm open to name suggestions.prepass
errors?Types of Changes
Changes
ssrPrepassEnabled
feature flag. The reason for calling itssrPrepassEnabled
as opposed to something with react query or use query in it, is because you could still use useQuery client side, there would be no way to stop partners from doing that, soenabledUseQuery
would be a misnomer.How to Test-Drive This PR
Checklists
General