-
Notifications
You must be signed in to change notification settings - Fork 896
feat: add usePaginatedQuery hook #10803
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
queryFn: ({ signal }) => API.getUsers(req, signal), | ||
cacheTime: 5 * 1000 * 60, |
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 function is still being used in one other place that doesn't require paginated data, so I can't get rid of it just yet
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'm trying to review kind of quickly since I'm on a time crunch this morning and have made you wait a couple days, but I just wanna make sure that this aligns with the caching stuff @BrunoQuaresma said he's doing
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.
Just double-checked, and the caching PR has been committed, so these can be safely removed. There wouldn't be any conflicts – they'd just be redundant
site/src/api/queries/audits.ts
Outdated
}, | ||
|
||
cacheTime: 5 * 1000 * 60, | ||
} as const satisfies UsePaginatedQueryOptions<AuditLogResponse, string>; |
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'd maybe just put this as the return type rather that use satisfies
queryFn: ({ signal }) => API.getUsers(req, signal), | ||
cacheTime: 5 * 1000 * 60, |
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'm trying to review kind of quickly since I'm on a time crunch this morning and have made you wait a couple days, but I just wanna make sure that this aligns with the caching stuff @BrunoQuaresma said he's doing
jest.clearAllMocks(); | ||
}); | ||
|
||
function render< |
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.
hooks don't really get "rendered" so to speak, so this name feels a bit weird. maybe it could be called testPaginatedQuery
or something? not a huge deal tho
* There are a lot of test cases in this file. Scoping mocking to inner describe | ||
* function calls to limit the cognitive load of maintaining all this stuff | ||
*/ | ||
describe(`${usePaginatedQuery.name} - Overall functionality`, () => { |
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.
nit: I feel like "overall functionality" is to be assumed if you're not mentioning something more specific
site/src/hooks/usePaginatedQuery.ts
Outdated
const firstPageOptions = getQueryOptionsFromPage(1); | ||
try { | ||
const firstPageResult = await queryClient.fetchQuery(firstPageOptions); | ||
fixedTotalPages = Math.ceil(firstPageResult?.count ?? 0 / limit) || 1; |
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.
fixedTotalPages = Math.ceil(firstPageResult?.count ?? 0 / limit) || 1; | |
fixedTotalPages = Math.max(Math.ceil(firstPageResult?.count ?? 0 / limit), 1); |
has a slightly different meaning, but if it wouldn't break anything I think the intent is clearer
site/src/hooks/usePaginatedQuery.ts
Outdated
try { | ||
const firstPageResult = await queryClient.fetchQuery(firstPageOptions); | ||
fixedTotalPages = Math.ceil(firstPageResult?.count ?? 0 / limit) || 1; | ||
} catch (err) { |
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.
} catch (err) { | |
} catch { |
no need to capture the error as a variable
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.
Oh, I had no idea that you could have a catch block without the error definition. I'll try not to abuse that too much
} | ||
|
||
const withoutPage = getParamsWithoutPage(searchParams); | ||
if (onInvalidPageChange === undefined) { |
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.
if (onInvalidPageChange === undefined) { | |
if (!onInvalidPageChange) { |
site/src/hooks/usePaginatedQuery.ts
Outdated
} | ||
|
||
const cleanedInput = clamp(Math.trunc(newPage), 1, totalPages ?? 1); | ||
if (!Number.isInteger(cleanedInput) || cleanedInput <= 0) { |
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 makes these checks necessary? the trunc
and clamp
should already guarantee these already?
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's still one check that needs to be done for NaN values – they won't throw errors for any of the math calculations
But you're right – one of the checks doesn't apply anymore, and the other can just turn into Number.isNaN
site/src/hooks/hookPolyfills.ts
Outdated
// useLayoutEffect should be overkill here 99% of the time, but it ensures it | ||
// will run before any other layout effects that need this custom hook |
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.
eh, you're just describing what useLayoutEffect
is for. maybe describe why it matters that this runs as a layout effect instead.
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, good point
site/src/utils/filters.ts
Outdated
export function prepareQuery(query: undefined): undefined; | ||
export function prepareQuery(query: string): string; | ||
export function prepareQuery(query?: string): string | undefined { |
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.
export function prepareQuery(query: undefined): undefined; | |
export function prepareQuery(query: string): string; | |
export function prepareQuery(query?: string): string | undefined { | |
export function prepareQuery<T extends string | undefined>(query: T): T extends undefined ? string | undefined : string { |
as this is defined, I believe you'd actually be unable to pass a value of type string | undefined
. could be "simplified"1 a bit to clear things up
Footnotes
-
type parameters are not simple but neither is "overloading" ↩
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.
So, I'm having trouble getting the compiler to be happy with your suggestion
I modified the overload signature to accept string | undefined
values, but at this point, yeah, probably better to refactor this to have better type signatures down the line
Closes #10589 (well 3/4 of the matters from the issue – auto-scrolling will be handled in a separate PR)
Changes made
usePaginatedQuery
hook, as well as associated utility types. The hook handles:prepareQuery
helper function to give it more specific return type infouseEffectEvent
to make sure that it still works properly if you use it withuseLayoutEffect
Notes
usePaginatedQuery
file is long – over 400 lines of code, with 1/3 runtime logic, 1/3 type definitions, and 1/3 comments to explain the type definitions. You might want to save it for lastuseSearchParams
hook and how it gives you new values every single time you call it. Other parts of the codebase are jumping through weird hoops to get around it, too, so I'll see if there's a safe way to make the values truly shared via context