Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: paginated data hook (#6333)
- Loading branch information
Showing
5 changed files
with
116 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 8 additions & 60 deletions
68
frontend/src/hooks/api/getters/useApplications/useApplications.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,10 @@ | ||
import useSWR, { mutate, SWRConfiguration } from 'swr'; | ||
import { useEffect, useState } from 'react'; | ||
import { formatApiPath } from 'utils/formatPath'; | ||
import handleErrorResponses from '../httpErrorResponseHandler'; | ||
import { ApplicationsSchema, GetApplicationsParams } from '../../../../openapi'; | ||
import { useClearSWRCache } from '../../../useClearSWRCache'; | ||
|
||
interface IUseApplicationsOutput extends ApplicationsSchema { | ||
refetchApplications: () => void; | ||
loading: boolean; | ||
error?: Error; | ||
} | ||
|
||
const PREFIX_KEY = 'api/admin/metrics/applications?'; | ||
|
||
const useApplications = ( | ||
params: GetApplicationsParams = {}, | ||
options: SWRConfiguration = {}, | ||
): IUseApplicationsOutput => { | ||
const urlSearchParams = new URLSearchParams( | ||
Array.from( | ||
Object.entries(params) | ||
.filter(([_, value]) => !!value) | ||
.map(([key, value]) => [key, value.toString()]), | ||
), | ||
).toString(); | ||
|
||
const KEY = `${PREFIX_KEY}${urlSearchParams}`; | ||
useClearSWRCache(KEY, PREFIX_KEY); | ||
|
||
const fetcher = async () => { | ||
return fetch(formatApiPath(KEY), { | ||
method: 'GET', | ||
}) | ||
.then(handleErrorResponses('Applications data')) | ||
.then((res) => res.json()); | ||
}; | ||
|
||
const { data, error } = useSWR(KEY, fetcher, { | ||
...options, | ||
}); | ||
|
||
const [loading, setLoading] = useState(!error && !data); | ||
|
||
const refetchApplications = () => { | ||
mutate(KEY); | ||
}; | ||
|
||
useEffect(() => { | ||
setLoading(!error && !data); | ||
}, [data, error]); | ||
|
||
return { | ||
applications: data?.applications || [], | ||
total: data?.total || 0, | ||
error, | ||
loading, | ||
refetchApplications, | ||
}; | ||
}; | ||
import { ApplicationsSchema } from '../../../../openapi'; | ||
import { createPaginatedHook } from '../usePaginatedData/usePaginatedData'; | ||
|
||
const prefixKey = 'api/admin/metrics/applications?'; | ||
const useApplications = createPaginatedHook<ApplicationsSchema>( | ||
{ applications: [], total: 0 }, | ||
prefixKey, | ||
); | ||
|
||
export default useApplications; |
40 changes: 40 additions & 0 deletions
40
frontend/src/hooks/api/getters/usePaginatedData/usePaginatedData.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { testServerRoute, testServerSetup } from 'utils/testServer'; | ||
import { render } from 'utils/testRenderer'; | ||
import { screen } from '@testing-library/react'; | ||
import { createPaginatedHook } from './usePaginatedData'; | ||
import { FC } from 'react'; | ||
import { http, HttpResponse } from 'msw'; | ||
|
||
const server = testServerSetup(); | ||
|
||
const usePaginatedData = createPaginatedHook<{ total: number; items: string }>( | ||
{ total: 0, items: 'default' }, | ||
'/api/project/my-project?', | ||
); | ||
|
||
const TestComponent: FC<{ query: string }> = ({ query }) => { | ||
const { items, total } = usePaginatedData({ query }); | ||
|
||
return ( | ||
<span> | ||
{items} ({total}) | ||
</span> | ||
); | ||
}; | ||
|
||
test('Pass query params to server and return total', async () => { | ||
testServerRoute(server, '/api/admin/ui-config', {}); | ||
server.use( | ||
http.get('/api/project/my-project', ({ request }) => { | ||
const url = new URL(request.url); | ||
return HttpResponse.json({ | ||
items: `result${url.searchParams.get('query')}`, | ||
total: 10, | ||
}); | ||
}), | ||
); | ||
render(<TestComponent query='value' />); | ||
|
||
await screen.findByText('default (0)'); | ||
const element = await screen.findByText('resultvalue (10)'); | ||
}); |
54 changes: 54 additions & 0 deletions
54
frontend/src/hooks/api/getters/usePaginatedData/usePaginatedData.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import useSWR, { SWRConfiguration } from 'swr'; | ||
import { formatApiPath } from 'utils/formatPath'; | ||
import handleErrorResponses from '../httpErrorResponseHandler'; | ||
import { useClearSWRCache } from '../../../useClearSWRCache'; | ||
|
||
type GenericSearchOutput<T> = { | ||
loading: boolean; | ||
initialLoad: boolean; | ||
error: string; | ||
total: number; | ||
} & T; | ||
|
||
export function createPaginatedHook<T extends { total?: number }>( | ||
customFallbackData: T, | ||
defaultPrefixKey = '', | ||
) { | ||
return ( | ||
params: Record<string, any> = {}, | ||
dynamicPrefixKey: string = '', | ||
options: SWRConfiguration = {}, | ||
): GenericSearchOutput<T> => { | ||
const urlSearchParams = new URLSearchParams( | ||
Array.from( | ||
Object.entries(params) | ||
.filter(([_, value]) => !!value) | ||
.map(([key, value]) => [key, value.toString()]), | ||
), | ||
).toString(); | ||
|
||
const prefix = dynamicPrefixKey || defaultPrefixKey; | ||
const KEY = `${prefix}${urlSearchParams}`; | ||
useClearSWRCache(KEY, prefix); | ||
|
||
const fetcher = async () => { | ||
return fetch(formatApiPath(KEY), { | ||
method: 'GET', | ||
}) | ||
.then(handleErrorResponses('Paginated data')) | ||
.then((res) => res.json()); | ||
}; | ||
|
||
const { data, error, isLoading } = useSWR(KEY, fetcher, { | ||
...options, | ||
}); | ||
|
||
const returnData = data || customFallbackData; | ||
return { | ||
...returnData, | ||
total: data?.total || 0, | ||
error, | ||
loading: isLoading, | ||
}; | ||
}; | ||
} |
90 changes: 12 additions & 78 deletions
90
frontend/src/hooks/api/getters/useProjectApplications/useProjectApplications.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,84 +1,18 @@ | ||
import useSWR, { SWRConfiguration } from 'swr'; | ||
import { useCallback } from 'react'; | ||
import { formatApiPath } from 'utils/formatPath'; | ||
import handleErrorResponses from '../httpErrorResponseHandler'; | ||
import { | ||
GetProjectApplicationsParams, | ||
ProjectApplicationsSchema, | ||
} from 'openapi'; | ||
import { useClearSWRCache } from 'hooks/useClearSWRCache'; | ||
import { ProjectApplicationsSchema } from 'openapi'; | ||
import { createPaginatedHook } from '../usePaginatedData/usePaginatedData'; | ||
|
||
type UseProjectApplicationsOutput = { | ||
loading: boolean; | ||
error: string; | ||
refetch: () => void; | ||
} & ProjectApplicationsSchema; | ||
|
||
const fallbackData: ProjectApplicationsSchema = { | ||
applications: [], | ||
total: 0, | ||
}; | ||
export const DEFAULT_PAGE_LIMIT = 25; | ||
|
||
const getPrefixKey = (projectId: string) => { | ||
return `api/admin/projects/${projectId}/applications?`; | ||
}; | ||
|
||
const createProjectApplications = () => { | ||
return ( | ||
projectId: string, | ||
params: GetProjectApplicationsParams, | ||
options: SWRConfiguration = {}, | ||
): UseProjectApplicationsOutput => { | ||
const { KEY, fetcher } = getProjectApplicationsFetcher( | ||
projectId, | ||
params, | ||
); | ||
|
||
const { data, error, mutate, isLoading } = | ||
useSWR<ProjectApplicationsSchema>(KEY, fetcher, options); | ||
|
||
const refetch = useCallback(() => { | ||
mutate(); | ||
}, [mutate]); | ||
|
||
const returnData = data || fallbackData; | ||
return { | ||
...returnData, | ||
loading: isLoading, | ||
error, | ||
refetch, | ||
}; | ||
}; | ||
}; | ||
|
||
export const DEFAULT_PAGE_LIMIT = 25; | ||
|
||
export const useProjectApplications = createProjectApplications(); | ||
|
||
const getProjectApplicationsFetcher = ( | ||
const useParameterizedProjectApplications = | ||
createPaginatedHook<ProjectApplicationsSchema>({ | ||
applications: [], | ||
total: 0, | ||
}); | ||
|
||
export const useProjectApplications = ( | ||
params: Record<string, any>, | ||
projectId: string, | ||
params: GetProjectApplicationsParams, | ||
) => { | ||
const urlSearchParams = new URLSearchParams( | ||
Array.from( | ||
Object.entries(params) | ||
.filter(([_, value]) => !!value) | ||
.map(([key, value]) => [key, value.toString()]), // TODO: parsing non-string parameters | ||
), | ||
).toString(); | ||
const KEY = `${getPrefixKey(projectId)}${urlSearchParams}`; | ||
useClearSWRCache(KEY, getPrefixKey(projectId)); | ||
const fetcher = () => { | ||
const path = formatApiPath(KEY); | ||
return fetch(path, { | ||
method: 'GET', | ||
}) | ||
.then(handleErrorResponses('Feature search')) | ||
.then((res) => res.json()); | ||
}; | ||
|
||
return { | ||
fetcher, | ||
KEY, | ||
}; | ||
}; | ||
) => useParameterizedProjectApplications(params, getPrefixKey(projectId)); |