-
Notifications
You must be signed in to change notification settings - Fork 13
Using RR loaders all over for clean page transitions #1101
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
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
868d175
to
0bc898f
Compare
const { data: vpc } = useApiQuery('vpcView', vpcParams) | ||
// could do `as Vpc`, but this keeps us more honest until they get Remix's | ||
// loader type inference into the router | ||
const vpc = useLoaderData() as Awaited<ReturnType<typeof loader>> |
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.
Interesting that this ends up being a one-line change in the component but the behavior is quite different:
- This can never fail to be present because this component will only render if the loader is successful
- Hence deleting the
?
below
- Hence deleting the
- The fetch has to be done before the page will render, so we don't see the layout first and then the page contents
0bc898f
to
f21c7a9
Compare
export const requireInstanceParams = requireParams('orgName', 'projectName', 'instanceName') | ||
export const requireVpcParams = requireParams('orgName', 'projectName', 'vpcName') | ||
export const requireProjectParams = requireParams('orgName', 'projectName') | ||
|
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.
could live without the helpers but they're kind of nice?
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.
Yeah, I think they're good. Shortens the params and makes them a bit explicit, so I'm good with it.
312ad17
to
2164f85
Compare
|
||
export const Router = () => ( | ||
<DataBrowserRouter fallbackElement={<span>loading</span>}> | ||
<DataBrowserRouter fallbackElement={null}> |
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 assume the null is what makes it wait before transitioning? Would the loading indicator be provided here somewhere?
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.
No, the null
is just telling it to show a blank screen while loading instead of flashing the word "loading". This is where you would drop a full-page loading state. I think a more integrated loading state like the little 1-2px bar across the top would have to be built right into the layout component using useNavigation().state
to tell when you're in a loading state.
loaders 4 all! you get a loader! you get a loader!
const colHelper = createColumnHelper<UserRow>() | ||
|
||
export const OrgAccessPage = () => { | ||
export function OrgAccessPage() { |
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.
changed all of these to use function
because of hoisting — it lets me define OrgAccessPage.loader
above rather than below
// used in useUserAccessRows to resolve user names | ||
apiQueryClient.prefetchQuery('userList', { limit: 200 }), | ||
]) | ||
} |
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 Promise.all
is nifty. so easy. I heard you like parallel loading so I put parallel loading in your parallel loading
const alreadyExistsBody = { error_code: 'ObjectAlreadyExists' } as const | ||
type AlreadyExists = typeof alreadyExistsBody | ||
const alreadyExistsErr = json(alreadyExistsBody, 400) | ||
const alreadyExistsErr = json(alreadyExistsBody, { status: 400 }) |
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.
Sorry for the noise in this file, you can basically ignore it. I was using the delay
option here a lot for testing so I made it easier to use
): ResponseTransformer<B> { | ||
const { status = 200, delay = 0 } = options | ||
return compose(context.status(status), context.json(body), context.delay(delay)) | ||
} |
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.
use an object instead of positional args for the options so you don't have to pass in a dummy status
in order to pass a delay
options | ||
) | ||
|
||
export const wrapQueryClient = <A extends ApiClient>(api: A, queryClient: 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.
fetch, prefetch, and refetch needed both api
and queryClient
here
} | ||
<A extends ApiClient>(api: A) => | ||
() => | ||
wrapQueryClient(api, useQueryClient()) |
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.
tbh we don't even really need this anymore since we can just import the right queryClient
directly. this one just makes it so you are pulling it off of context from the QueryClientProvider
. Not sure what the point of that is. Maybe if you had different query clients for different sections of the route tree and you wanted the component to transparently pull the right one, but my god why would you do that?
|
||
// to be used in loaders, which are outside the component tree and therefore | ||
// don't have access to context | ||
export const apiQueryClient = wrapQueryClient(api.methods, 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.
this is important, we're using this for all the prefetch calls
+ }, []); | ||
return null; | ||
} | ||
|
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.
pulling the fix from this PR. I will remove the patch as soon as they release this (should be this week)
remix-run/react-router#9124
limit: 10, | ||
}) | ||
} | ||
|
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.
These are all extremely straightforward. The big problem I see is making sure you are actually making the right request with the right params. I left the wrong method in a few times and it wasn't always easy to notice. Params are even worse — the limit: 10
I stuck everywhere is to match QueryTable
but it's way too easy to mess up. We can think about it.
I started writing a doc for the https://github.com/oxidecomputer/console/blob/data-arch-doc/docs/data-fetching.md |
@david-crespo on the RFD note... I'm uncertain. Does it require a discussion? It feels like internal documentation might be the better way to tackle it. If we were outlining general principles or something that could always be an RFD, but this seems like it'd be more cost than gain. |
It does not require discussion. L'état, c'est moi. |
This started out being about making error handling easier, but now that I figured out how to call
prefetchQuery
in a loader, I'm going around adding loaders to most pages so there is no visible pop-in when the API call is not instantaneous.requireParams
, a version ofuseRequiredParams
that works in loaders, where we getparams
as an arg instead of callinguseParams
useRequiredParams
now has a one-line definition because all the logic is inrequireParams
queryClient
into@oxide/api
so we canqueryClient.fetchQuery
with an API likeuseApiQuery
What loaders do for page transitions
Both of these are with a 1s sleep in the VPC endpoint handler to simulate a slow response.
Before: render, then fetch
After: fetch first
React Router holds off on the route transition until the loader is done. Much more like a traditional web app despite still being all client-side.