I have a function like this:
function refactorQueryResult<D, E>(
name: string,
original: UseQueryResult<D, E>
) {
return {
[name]: original.data,
[`${name}UpdatedAt`]: original.dataUpdatedAt,
[`${name}Error`]: original.error,
[`${name}ErrorUpdatedAt`]: original.errorUpdatedAt,
[`${name}FailureCount`]: original.failureCount,
[`${name}HasError`]: original.isError,
[`${name}Fetched`]: original.isFetched,
[`${name}FetchedAfterMount`]: original.isFetchedAfterMount,
[`${name}Fetching`]: original.isFetching,
[`${name}Idle`]: original.isIdle,
[`${name}Loading`]: original.isLoading,
[`${name}HasLoadingError`]: original.isLoadingError,
[`${name}IsPlaceholder`]: original.isPlaceholderData,
[`${name}IsPrevious`]: original.isPreviousData,
[`${name}HasRefetchError`]: original.isRefetchError,
[`${name}Refetching`]: original.isRefetching,
[`${name}HasSuccess`]: original.isSuccess,
[`${name}IsStale`]: original.isStale,
[`fetch${name[0].toUpperCase()}${name.slice(1)}`]: original.refetch,
[`remove${name[0].toUpperCase()}${name.slice(1)}`]: original.remove,
[`${name}Status`]: original.status,
};
}
But to my dismay, when I use the result:
const { cards } = refactorQueryResult("cards", useQuery(listCards(filters)))
Typescript complains that cards does not exists, but of course it does.
Is there a way to type hint the return value?
Keep in mind I do realize I can just do:
const { data: cards, isFetched: cardsFetched /*, etc */ } = useQuery(listCards(filters))
But I'm both lazy and curious, so I am wondering if there isn't a way to waste a couple of processor cycles so I have to type less.
For a much simpler function, I've tried:
function renameData<D, E>(
name: string,
queryResult: UseQueryResult<D, E>
): { [name]: D } & UseQueryOptions<D, E> {
} & UseQueryResult<D, E> {
return { [name]: queryResult.data, ...queryResult }
}
But here typescript complains: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type
I think it is probably not possible without adding another type parameter. So something like:
export function refactorQueryResult<T, D, E>(
name: string,
original: UseQueryResult<D, E>
): ComplicatedTemplateMapping<T, UseQueryResult<D, E>> {
// ...
}
// Then elsewhere
const { cards, cardsFetched } = refactorQueryResult<{ cards }>(
"cards", useQuery(listCards(filters))
)
But here I'm not sure how to even begin with the checks and slicing in ComplicatedTemplateMapping
Please consider approach with mapping:
interface UseQueryResult<TData = unknown, TError = unknown> {
data: TData | undefined;
dataUpdatedAt: number;
error: TError | null;
errorUpdatedAt: number;
failureCount: number;
isError: boolean;
}
function refactorQueryResult<D, E, T extends string>(
name: T,
original: UseQueryResult<D, E>
): TransformedData<UseQueryResult<D, E>, T> {
return {
[name]: original.data,
[`${name}UpdatedAt`]: original.dataUpdatedAt,
[`${name}Error`]: original.error,
[`${name}ErrorUpdatedAt`]: original.errorUpdatedAt,
[`${name}FailureCount`]: original.failureCount,
[`${name}HasError`]: original.isError,
};
}
type MappedData<N extends string> = {
data: N,
dataUpdatedAt: `${N}UpdatedAt`,
error: `${N}Error`,
errorUpdatedAt: `${N}ErrorUpdatedAt`,
failureCount: `${N}FailureCount`
isError: `${N}HasError`
}
type TransformedData<QR, N extends string> = {
[P in keyof QR as P extends keyof MappedData<N> ? MappedData<N>[P] : never]: QR[P]
}
function test(queryResult: UseQueryResult) {
const {
test,
testError,
testUpdatedAt,
testErrorUpdatedAt,
testHasError,
} = refactorQueryResult("test", queryResult)
}