felixmosh
felixmosh

Reputation: 35493

Typescript: Conditional return type based on arguments props

I have a function which I want it to return 2 type different types based on its argument props.

interface IPaginateParams {
  perPage: number;
  currentPage: number;
  isFromStart?: boolean;
}

interface IWithPagination<Data, TParams extends IPaginateParams = IPaginateParams> {
  data: Data;
  pagination: IPagination<TParams>;
}

type IPagination<TParams> = TParams extends
  | { currentPage: 1 }
  | { isFromStart: true }
  | { isLengthAware: true }
  ? ILengthAwarePagination
  : IBasePagination;

interface IBasePagination {
  currentPage: number;
  perPage: number;
  from: number;
  to: number;
}

interface ILengthAwarePagination extends IBasePagination {
  total: number;
  lastPage: number;
}

function paginate<TData = any[], TParams extends IPaginateParams = IPaginateParams>(
  options: TParams
): IWithPagination<TData, TParams>;

The idea is that if you pass currentPage: 1 or isFromStart: true, it should add 2 additional types to the pagination object.

The weird thing is that IWithPagination works as expected,

const data = {} as IWithPagination<any, {perPage: 2, currentPage: 1}>;

expectType<ILengthAwarePagination>(data.pagination);

But when I use the invocation, it always return the IBasePagination

const data = paginate({perPage: 2, currentPage: 1});

expectType<ILengthAwarePagination>(data.pagination) // fails

// or

const data = paginate({perPage: 2, currentPage: 2, isFromStart: true});

expectType<ILengthAwarePagination>(data.pagination) // fails

Playground

Upvotes: 2

Views: 339

Answers (1)

felixmosh
felixmosh

Reputation: 35493

As @OlegValter explained in the comments, when passing an object to paginate it inferred as a widen type, for example:

{perPage: 2, currentPage: 2, isFromStart: true} // inferred as {perPage: number; currentPage: number; isFromStart: boolean}

Therefore, the check in the return type always fallback to the IBasePagination type (the else clause).

All we need to do, is to specify that the arguments of the function are readonly.

declare function paginate<TData = any[], TParams extends IPaginateParams = IPaginateParams>(
  options: Readonly<TParams>
  // ---------^ this is what made the input as a narrow type
): IWithPagination<TData, TParams>;

Working example

Upvotes: 1

Related Questions