Typescript: Dynamic return type determined by argument properties

I have a function that should return two 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 concept is that if you specify currentPage: 1 or isFromStart: true, it should include two additional types in the pagination object.

Interestingly, IWithPagination behaves as intended,

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

expectType<ILengthAwarePagination>(data.pagination);

However, when using the function, it always returns 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

Answer №1

According to @OlegValter's explanation in the comments, when an object is passed to paginate, it is interpreted as a widened type. For example:

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

As a result, the return type check always defaults to the IBasePagination type (the else clause).

All that is needed is to specify that the function arguments 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>;

Check out a working example

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Receiving an error with React Proptypes when using the union type Breakpoint

Struggling to assign the correct proptype to the material-ui Breakpoint type. The breakpoint values are: export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; In my App.tsx file, I have the following ...

REDUX: The dispatch function is failing to update the store

Working on a project developing a chrome extension that involves dispatching functions in popup.tsx. However, the store does not update when I try to dispatch. Interestingly, the same code works perfectly fine in the background page. Any suggestions on wha ...

Issue with Jest/SuperTest Express integration tests: encountering "Can't set headers after they are sent" error when making requests to the same endpoint in multiple test cases

Dealing with a tricky situation here.. I'm currently in the process of writing integration tests for an Express (Typescript) app, using Jest and Supertest. My challenge lies in having multiple tests for the same endpoint, specifically to test respon ...

Mastering the Art of Mocking Asynchronous Methods in Node.js Using Jest

I have the following files: |_ utils.js |_methods.js I am currently conducting unit testing for rest.js methods, where the content of the file is as follows: methods.js import Express from 'express' import { add_rec } from './utils' ...

Issue with using third-party package types in Angular library creation

My project involves creating a custom voice recognition library, and I have decided to utilize 3rd party package types called @types/dom-speech-recognition. However, upon attempting to integrate these types into my service, the compiler raised errors indic ...

Troubleshooting issue with getServerSideProps not functioning in Next.js while utilizing Next-redux-wrapper and TypeScript

When attempting to trigger an action as outlined in the documentation using the getServerSideProps function with the help of next-redux-wrapper store and redux-thunk, I am encountering the following TypeScript error: ts(2322): Type '({ req }: GetServe ...

Hiding the line connector between data points in ChartJs

I recently took over a project that includes a line chart created using Chart.js by the previous developer. My client has requested that I do not display a line between the last two data points. Is this possible with Chart.js? I have looked through the doc ...

Transforming a TypeScript enum into an array of objects

My enum is defined in this structure: export enum GoalProgressMeasurements { Percentage = 1, Numeric_Target = 2, Completed_Tasks = 3, Average_Milestone_Progress = 4, Not_Measured = 5 } However, I want to transform it into an object ar ...

The term 'ObjectId' is typically used as a type, but in this context it is being incorrectly used as a value

I've been scouring for an answer without success. As a newcomer to both stackoverflow and typescript, I've encountered an issue while creating a Mongoose Schema. Here's a snippet of my code: import { Schema, ObjectId } from 'mongoose&a ...

What is the best way to send {...rest} properties to a text field in react material?

When using a material textfield inside a wrapper component and passing the remaining props as {...otherprops} in a JavaScript file, everything works fine. However, when attempting to do the same in TypeScript, an error occurs. const TextFieldWrapper = (pro ...

How can you utilize an injected service from inside a Class decorator in Typescript/NestJS?

Can an injected service be accessed from within a Class decorator? I aim to retrieve the service in the personalized constructor: function CustDec(constructor: Function) { var original = constructor; var f: any = function (...args) { // How can I ...

Whenever I try to execute 'docker build --no-cache -t chat-server .', I always encounter type errors

Below is the Dockerfile located in the root directory of my express server: FROM node:18 WORKDIR /usr/src/server COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 RUN npm run build CMD ["npm", "start"] Here is the contents of my .dockerign ...

Send a function as a parameter to another component, but it remains dormant

I am attempting to control the enable and disable state of a button based on changes in a value. To achieve this, I have defined a model as follows: export class Model{ label:string=''; isEnabled:Function=()=>true; } The component1 i ...

getting TypeScript configured with webpack

I am currently using Typescript to develop a back-end API utilizing graphql and express. To manage the project development and building process, I have implemented webpack. As part of my setup, I am employing raw-loader in order to load graphql schemas an ...

How can I customize a currency directive in AngularJS using filters?

My goal is to enhance user experience by allowing input in custom currency values like '1.5M' instead of 1500000, and '1B' instead of 1000000000 on an input form dealing with large numbers. To achieve this, I've created a FormatSer ...

Leverage tsconfig.json for TypeScript compilation in Vim using the Syntastic plugin

How can I configure the syntastic plugin in vim to provide live error checking for TypeScript files using tsc? Currently, even though I have tsc set up in vim, it doesn't seem to be using the closest parent's tsconfig.json file for configuration. ...

An error occurred while trying to add a property to an array because the object is not extensible: TypeError -

In my code, there is an object named curNode with the following structure: { "name": "CAMPAIGN", "attributes": {}, "children": [] } I am attempting to add a new node to the object like this: curNode!.children!.push({ name: newNodeName, ...

What is the best way to send ServerSideProps to a different page in Next.js using TypeScript?

import type { NextPage } from 'next' import Head from 'next/head' import Feed from './components/Feed'; import News from './components/News'; import Link from 'next/link'; import axios from 'axios&apo ...

Experiencing an error message stating 'The token ${Token[TOKEN.72]} is invalid' while using cdk synth, specifically when trying to assign the value of ec2.Vpc.cidr from cfnParameter.value

Attempting to utilize the AWS CDK CfnParameter to parameterize the CIDR value of ec2.Vpc. The aim is to make the stack reusable for VPC creation with the CIDR as a customizable value. An error stating "${Token[TOKEN.72]} is not valid" occurs during synthe ...

The 'Alias[]' type does not share any properties with the 'Alias' type

I encountered an issue: The error message 'Type 'Alias[]' has no properties in common with type 'Alias'' appeared. Here is my Alias setup: alias: Alias = { id: 0, domain_id: 0, source: '', dest ...