Developing a function that determines its return type based on the presence of an optional parameter

Let's explore a JavaScript function and its usage:

const fooHandler = function(state, foo) {
  if (foo) {
    return {...state, foo}
  } else {
    return state.foo
  }
}

const currentState = {foo: 'foo', other: 'data'};
fooHandler(currentState); // returns 'foo'
fooHandler(currentState, 'bar'); // return {foo: 'bar', other: 'data'}

// This function can be used to interact with state dynamically
function handleInteraction(state, handlerFn) {
  // e.g. handlerFn(state);
}

handleInteraction(currentState, fooHandler);

How would I implement this in TypeScript?

I need to define a type that satisfies Typescript requirements:

type StateHandler {
  (state: State): string
  (state: State, value: string): State
}
function performInteraction(state: State, stateFn: StateHandler) {
  const data: string = stateFn(state);
  const updatedState: State = stateFn(state, data);
  // etc
}

However, there are challenges in implementing StateHandler:

const fooHandler: StateHandler = function(state: State, foo?: string) {
  if (foo) {
    return {...state, foo} as State
  } else {
    return state.foo as string
  }
}
/*
Type '(state: State, foo?: string) => string | State' is not assignable to type 'StateHandler'.
  Type 'string | State' is not assignable to type 'string'.
    Type 'State' is not assignable to type 'string'.ts(2322)
*/

Arrow functions/lambda expressions also present difficulties.

One workaround is using function overloading:

function fooHandler(state: State): string
function fooHandler(state: State, value: string): State
function fooHandler(state: State, value?: string) {
  if (value) {
    return {...state, foo: value} as State
  } else {
    return state.foo as string
  }
}

// This approach works, but it feels clunky and repetitive
const typedFooHandler: StateHandler = fooHandler
performInteraction(someState, typedFooHandler)

This method is somewhat awkward and necessitates redefining the StateHandler type with every implementation.

Answer №1

To simplify the overload logic, we can incorporate it into a generic function signature utilizing Generic Parameter Defaults along with a Conditional Type to determine the return value outcome. Check out the Playground version for demonstration.

The assignment

V extends string | undefined = undefined
defines that V is either of type string or undefined by default being set as undefined. The conditional statement
V extends string ? State : string
utilizes ternary syntax to determine the output based on the type of V.

type State = Record<string, any>

// Create a function whose return type depends on an input argument
type StateInteractor = <S extends State, V extends string | undefined = undefined>(state: S, value?: V) => V extends string ? State : string 

// Define a function instance
const fooInteractor: StateInteractor = (state: State, foo?: string) => {
  if (foo) {
    return {...state, foo}
  } else {
    return state.foo
  }
}

// Test the function
const state = {foo: 'foo', other: 'data'};
const shouldBeFoo = fooInteractor(state); // Returns 'foo'
const shouldBeState = fooInteractor(state, 'bar'); // Returns {foo: 'bar', other: 'data'}

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

acquiring environmental variables in TypeScript for node applications

I am struggling with accessing process.env variables in my TypeScript pages. It seems to be a scope issue, which doesn't make sense to me as a beginner in TypeScript. To get my environment variables, I use a YAML file and attach them to the running p ...

Limiting the parameter type in Node.js and TypeScript functions

Currently working on a Node.js project utilizing TypeScript. I'm attempting to limit the argument type of a function to a specific base class. As a newcomer to both Node and TypeScript with a background in C#, I may not fully grasp some of the langua ...

Tips for formatting numbers within a chart

One issue in my chart is that the values are not formatted correctly. For instance, I have a number 15900 and I would like it to be displayed as 15 900 with a space between thousands and hundreds. Below is the code snippet I would like to implement for s ...

Dynamically defined type literals in Typescript

Recently, I came across an intriguing problem. I am attempting to develop a Vue.js wizard component that takes in configuration objects. The type of demo I have looks like this: type WizardConfiguration = { steps: Array<{ name: string, fie ...

Using TypeScript to create a state object in Koa

I am currently utilizing Koa () as the framework for my backend, which consists of Node.js + TypeScript. Koa permits and recommends using the built-in `ctx.state` to store and pass data between different middleware functions. I have been adhering to this ...

Unexpected error in React TypeScript: '=' symbol missing. Code error TS1005

Encountering an issue: '=' expected. TS1005 5 | * LICENSE file in the root directory of this source tree. 6 | */ > 7 | import type { NewPlugin, Optionsz, OptionsReceived } from './types'; | ^ ...

Utilizing TypeScript Generics to Dynamically Set Tag Names in React

I am working on a straightforward polymorphic React component that is designed to render only tag names (such as span) and not custom React components (like MyComponent). I believe this can be achieved using JSX.IntrinsicElements. Here is the code snippet ...

Error in Visual Studio with Angular 2 build: 'Promise' name not found

I recently started exploring Angular2 and followed the instructions provided in this quickstart guide: https://angular.io/guide/quickstart Everything seems to be working well after running npm install, but now I want to work on it within Visual Studio usi ...

Exploring the Power of Modules in NestJS

Having trouble with this error - anyone know why? [Nest] 556 - 2020-06-10 18:52:55 [ExceptionHandler] Nest can't resolve dependencies of the JwtService (?). Check that JWT_MODULE_OPTIONS at index [0] is available in the JwtModule context. Possib ...

Trigger change event on model update in Angular 4 checkboxes

CSS <div class="checkbox-item"> <input type="checkbox" id="1" [(ngModel)]="filter" (change)="onFilterChange($event)"> CheckBox </div> <button (click)="filter = !filter">Change Status</button> JavaScript export class Filt ...

How do I adjust brightness and contrast filters on a base64 URL?

When presented with an image in base64 format like this: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABDAAwfqNRk/rjcYX+PxrV/wtWqwJSTlboMgAAAABJRU5ErkJggg== What is the most efficient method to programmatically alter a filter (such as brightness or cont ...

An issue has occurred: Unable to locate a supporting object 'No result' of type 'string'. NgFor is only compatible with binding to Iterables like Arrays

I am attempting to utilize this code to post data from a web service. service.ts public events(id: string): Observable<Events> { ...... return this.http.post(Api.getUrl(Api.URLS.events), body, { headers: headers }) .map((re ...

Is it possible to use export default Enum in TypeScript?

I am facing an issue with exporting an enum object as default at the top level in my code. Here is what I tried: export default enum Hashes{ FOO = 'foo', BAR = 'bar', } However, this resulted in an error message: Module parse failed ...

A generic function in Typescript that can accept an object with the first argument as a specified key

I am looking to create a function that will take a string as its first argument and will only accept a second argument of type object if it contains the first argument as a key with a boolean value: const checkFlag = (str:string, obj) => obj[str] Alth ...

The error TS2339 is indicating that there is no property called myProperty on the type SetStateAction<User>

I'm encountering a TypeScript error while working with React that's leaving me puzzled: <html>TS2339: Property 'subEnd' does not exist on type 'SetStateAction&lt;User&gt;'.<br/>Property 'subEnd' d ...

Dealing with Unexpected Timeout Errors and Memory Leaks in Express/Typescript Using Jest, Supertest, and TypeORM

Currently, I am in the process of writing unit tests for an express API. Each test suite initiates a fresh instance of an express server before running the tests. Individually, each test suite runs smoothly without any problems. However, when executed tog ...

Issue with change detection in Angular array authentication

I am currently working with a parent / child relationship consisting of two components: "Properties" - the parent, and "listingsList" - the child. Below is the parent template: <div class="row"> <div class="col-9 p-2"> ...

Key factors to keep in mind when comparing JavaScript dates: months

Check the dates and determine if the enddate refers to the following month by returning a boolean value. Example startdate = January 15, 2020 enddate = February 02, 2020 Output : enddate is a future month startdate = January 15, 2020 enddate = January 2 ...

Converting data types of a destructured property

In my Next.js application, I'm using a router hook and destructuring like this: const { query: { run_id }, } = useRouter(); The type of `run_id` is as follows: run_id: string | string[] | undefined I tried to typecast it as shown below, but it doe ...

Moving the marker does not update the address

When the dragend event is triggered, the Getaddress function will be called in the following code: Getaddress(LastLat, LastLng , marker,source){ this.http.get('https://maps.googleapis.com/maps/api/geocode/json?latlng='+LastLat+ &apos ...