Search code examples
typescriptmapped-types

Return type with property with same name as parameter value


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


Solution

  • 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)
    }
    
    

    Playground