-
-
Save AlexMachin1997/6462731447b82eaf15575043184151e0 to your computer and use it in GitHub Desktop.
A.ts uses generics without inferring from a utility function response. B.ts uses generics with infering the result from the newResource utility function response.
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
import * as React from 'react'; | |
import { useQuery, QueryKey } from '@tanstack/react-query'; | |
import axios, { AxiosRequestHeaders } from 'axios'; | |
import { newResource, Resource } from '@models/resource'; | |
const INITIAL_STATE = { | |
page: 1, | |
resultsPerPage: 20, | |
sort: '', | |
order: '' | |
}; | |
type ACTION_TYPE = | |
| { type: 'CHANGE_PAGE_NUMBER'; payload: { newPageNumber: number } } | |
| { type: 'CHANGE_SORT'; payload: { newSort: string } } | |
| { type: 'CHANGE_ORDER'; payload: { newOrder: string } } | |
| { type: 'CHANGE_ITEMS_PER_PAGE'; payload: { newNumberOfItemsPerPage: number } }; | |
type APP_STATE = { | |
page: number; | |
resultsPerPage: number; | |
sort: string; | |
order: string; | |
}; | |
type AxiosResponse<TData> = { | |
content: TData[]; | |
pageable: { | |
size: number; | |
number: number; | |
sort: Object; | |
}; | |
totalSize: number; | |
}; | |
type UseResourceParams<T> = { | |
isQueryEnabled: boolean; | |
baseQueryKey: string; | |
defaultValues?: APP_STATE; | |
initialData?: Resource<T>; | |
url: string; | |
accessToken: string | null; | |
}; | |
const reducer = (state: APP_STATE, action: ACTION_TYPE) => { | |
switch (action.type) { | |
case 'CHANGE_PAGE_NUMBER': { | |
const { newPageNumber } = action.payload; | |
return { | |
...state, | |
page: newPageNumber | |
}; | |
} | |
case 'CHANGE_SORT': { | |
const { newSort } = action.payload; | |
return { | |
...state, | |
sort: newSort | |
}; | |
} | |
case 'CHANGE_ORDER': { | |
const { newOrder } = action.payload; | |
return { | |
...state, | |
order: newOrder | |
}; | |
} | |
case 'CHANGE_ITEMS_PER_PAGE': { | |
const { newNumberOfItemsPerPage } = action.payload; | |
return { | |
...state, | |
resultsPerPage: newNumberOfItemsPerPage | |
}; | |
} | |
default: { | |
return { ...state }; | |
} | |
} | |
}; | |
type A<T> = { | |
content: T[]; | |
meta: { | |
from: number; | |
to: number; | |
total: number; | |
size: number; | |
last_page: number; | |
current_page: number; | |
}; | |
totalSize: number; | |
}; | |
const useResource = <T>({ | |
isQueryEnabled, | |
baseQueryKey, | |
defaultValues, | |
initialData, | |
url, | |
accessToken | |
}: UseResourceParams<T> & { [key: string]: unknown }) => { | |
const [state, dispatch] = React.useReducer( | |
// Reducer for managing state | |
reducer, | |
// Inital state | |
{ | |
// Copy the initial state, could contain additional values | |
...INITIAL_STATE, | |
// Override certain state properties, as there could initial pieces of state from NextJS | |
page: defaultValues?.page ?? INITIAL_STATE.page, | |
resultsPerPage: defaultValues?.resultsPerPage ?? INITIAL_STATE.resultsPerPage, | |
sort: defaultValues?.sort ?? INITIAL_STATE.sort, | |
order: defaultValues?.order ?? INITIAL_STATE.order | |
}, | |
// A callback incase you want to do additional with the state or whatever | |
(existingState) => ({ ...existingState }) | |
); | |
// Generate the qu ery key, this is exposed as you could use it for mutations | |
const generatedQueryKey: QueryKey = React.useMemo( | |
() => [ | |
{ | |
// Base key e.g. "audits", "hostlmss" etc | |
key: baseQueryKey, | |
// When should the query re-run automatically ?? | |
dependencies: { | |
page: state.page, | |
resultsPerPage: state.resultsPerPage, | |
sort: state.sort, | |
order: state.order | |
} | |
} | |
], | |
[baseQueryKey, state.order, state.page, state.resultsPerPage, state.sort] | |
); | |
const { status, isRefetching, fetchStatus, data } = useQuery<A<T>, Error>({ | |
queryKey: generatedQueryKey, | |
queryFn: async () => { | |
try { | |
let headers: AxiosRequestHeaders = {}; | |
// If a token is provided attach it to the Axios Headers | |
if (accessToken !== null) { | |
headers = { | |
...headers, | |
Authorization: 'Bearer ' + accessToken | |
}; | |
} | |
// Perform the request and wait for the response to resolve | |
const response = await axios.get<AxiosResponse<T>>(url, { | |
headers: { ...headers } | |
}); | |
// If the status isn't 200 then throw an error to put the query in rejected mode | |
if (response.status !== 200) { | |
throw Error(); | |
} | |
// Return the data in the approproiate format | |
return Promise.resolve( | |
newResource<T>(response.data.content, response.data.pageable, response.data.totalSize) | |
); | |
} catch { | |
return Promise.reject(`Failed to perform a fetch request for ${generatedQueryKey}`); | |
} | |
}, | |
enabled: isQueryEnabled, | |
initialData: initialData, | |
keepPreviousData: true | |
}); | |
return { | |
// Return the actual data | |
resource: data, | |
// Return the key incase it's needed for a mutation | |
queryKey: generatedQueryKey, | |
// Status contains error or success | |
status: status, | |
// In TanStack query v4 the idle status is found in the fetchStatus, this will probably change in v5 or may not who knows but Typescript will let us know | |
isIdle: fetchStatus === 'idle', | |
// A small bool flag to indicate if a refetch is appearing, can happen automatically in the background | |
isRefetching, | |
// Used for interacting with the react hook and re-running queries e.g. updating the page number will fetch the next page | |
state, | |
dispatch | |
}; | |
}; | |
export default useResource; |
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
import * as React from 'react'; | |
import { useQuery, QueryKey } from '@tanstack/react-query'; | |
import axios, { AxiosRequestHeaders } from 'axios'; | |
import { newResource, Resource } from '@models/resource'; | |
const INITIAL_STATE = { | |
page: 1, | |
resultsPerPage: 20, | |
sort: '', | |
order: '' | |
}; | |
type ACTION_TYPE = | |
| { type: 'CHANGE_PAGE_NUMBER'; payload: { newPageNumber: number } } | |
| { type: 'CHANGE_SORT'; payload: { newSort: string } } | |
| { type: 'CHANGE_ORDER'; payload: { newOrder: string } } | |
| { type: 'CHANGE_ITEMS_PER_PAGE'; payload: { newNumberOfItemsPerPage: number } }; | |
type APP_STATE = { | |
page: number; | |
resultsPerPage: number; | |
sort: string; | |
order: string; | |
}; | |
type AxiosResponse<TData> = { | |
content: TData[]; | |
pageable: { | |
size: number; | |
number: number; | |
sort: Object; | |
}; | |
totalSize: number; | |
}; | |
type UseResourceParams<T> = { | |
isQueryEnabled: boolean; | |
baseQueryKey: string; | |
defaultValues?: APP_STATE; | |
initialData?: Resource<T>; | |
url: string; | |
accessToken: string | null; | |
}; | |
const reducer = (state: APP_STATE, action: ACTION_TYPE) => { | |
switch (action.type) { | |
case 'CHANGE_PAGE_NUMBER': { | |
const { newPageNumber } = action.payload; | |
return { | |
...state, | |
page: newPageNumber | |
}; | |
} | |
case 'CHANGE_SORT': { | |
const { newSort } = action.payload; | |
return { | |
...state, | |
sort: newSort | |
}; | |
} | |
case 'CHANGE_ORDER': { | |
const { newOrder } = action.payload; | |
return { | |
...state, | |
order: newOrder | |
}; | |
} | |
case 'CHANGE_ITEMS_PER_PAGE': { | |
const { newNumberOfItemsPerPage } = action.payload; | |
return { | |
...state, | |
resultsPerPage: newNumberOfItemsPerPage | |
}; | |
} | |
default: { | |
return { ...state }; | |
} | |
} | |
}; | |
type A<T> = { | |
content: T[]; | |
meta: { | |
from: number; | |
to: number; | |
total: number; | |
size: number; | |
last_page: number; | |
current_page: number; | |
}; | |
totalSize: number; | |
}; | |
const useResource = <T>({ | |
isQueryEnabled, | |
baseQueryKey, | |
defaultValues, | |
initialData, | |
url, | |
accessToken | |
}: UseResourceParams<T> & { [key: string]: unknown }) => { | |
const [state, dispatch] = React.useReducer( | |
// Reducer for managing state | |
reducer, | |
// Inital state | |
{ | |
// Copy the initial state, could contain additional values | |
...INITIAL_STATE, | |
// Override certain state properties, as there could initial pieces of state from NextJS | |
page: defaultValues?.page ?? INITIAL_STATE.page, | |
resultsPerPage: defaultValues?.resultsPerPage ?? INITIAL_STATE.resultsPerPage, | |
sort: defaultValues?.sort ?? INITIAL_STATE.sort, | |
order: defaultValues?.order ?? INITIAL_STATE.order | |
}, | |
// A callback incase you want to do additional with the state or whatever | |
(existingState) => ({ ...existingState }) | |
); | |
// Generate the qu ery key, this is exposed as you could use it for mutations | |
const generatedQueryKey: QueryKey = React.useMemo( | |
() => [ | |
{ | |
// Base key e.g. "audits", "hostlmss" etc | |
key: baseQueryKey, | |
// When should the query re-run automatically ?? | |
dependencies: { | |
page: state.page, | |
resultsPerPage: state.resultsPerPage, | |
sort: state.sort, | |
order: state.order | |
} | |
} | |
], | |
[baseQueryKey, state.order, state.page, state.resultsPerPage, state.sort] | |
); | |
const { status, isRefetching, fetchStatus, data } = useQuery< | |
ReturnType<typeof newResource<T>>, | |
Error | |
>({ | |
queryKey: generatedQueryKey, | |
queryFn: async () => { | |
try { | |
let headers: AxiosRequestHeaders = {}; | |
// If a token is provided attach it to the Axios Headers | |
if (accessToken !== null) { | |
headers = { | |
...headers, | |
Authorization: 'Bearer ' + accessToken | |
}; | |
} | |
// Perform the request and wait for the response to resolve | |
const response = await axios.get<AxiosResponse<T>>(url, { | |
headers: { ...headers } | |
}); | |
// If the status isn't 200 then throw an error to put the query in rejected mode | |
if (response.status !== 200) { | |
throw Error(); | |
} | |
// Return the data in the approproiate format | |
return Promise.resolve( | |
newResource<T>(response.data.content, response.data.pageable, response.data.totalSize) | |
); | |
} catch { | |
return Promise.reject(`Failed to perform a fetch request for ${generatedQueryKey}`); | |
} | |
}, | |
enabled: isQueryEnabled, | |
initialData: initialData, | |
keepPreviousData: true | |
}); | |
return { | |
// Return the actual data | |
resource: data, | |
// Return the key incase it's needed for a mutation | |
queryKey: generatedQueryKey, | |
// Status contains error or success | |
status: status, | |
// In TanStack query v4 the idle status is found in the fetchStatus, this will probably change in v5 or may not who knows but Typescript will let us know | |
isIdle: fetchStatus === 'idle', | |
// A small bool flag to indicate if a refetch is appearing, can happen automatically in the background | |
isRefetching, | |
// Used for interacting with the react hook and re-running queries e.g. updating the page number will fetch the next page | |
state, | |
dispatch | |
}; | |
}; | |
export default useResource; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment