Defining optional parameters in TypeScript

Currently, I am working on implementing strong typing for a flux framework (specifically Vuex). Here is my current code:

const actions = {
  first(context: Context, payload: string) { return doSomething(context, payload); },
  second(context: Context) { return doSomethingWithNoPayload(context); }
}

type Actions = typeof actions;
type PayloadType<A extends keyof Actions> = Parameters<Actions[A]>[1];

function dispatch<A extends keyof Actions>(action: A): ReturnType<Actions[A]>;
function dispatch<A extends keyof Actions>(action: A, payload: Payload<A>): ReturnType<Actions[A]>;
function dispatch<A extends keyof Actions>(action: A, payload: Payload<A> = undefined): Promise<any> {
  return Promise.resolve({ action, payload });
}

The goal of this implementation is to achieve the following behavior:

dispatch("first") // error, payload not specified
dispatch("first", false) // error, incorrect payload type
dispatch("first", "correct") // correct, valid payload type

dispatch("second", "something") // error, should not accept payload
dispatch("second") // correct, no payload needed
dispatch("third") // error, non-existent action

Using an optional parameter for the payload is causing issues because it does not enforce passing the payload to the "first" action which requires one. On the other hand, if the payload is not declared as optional, then calling dispatch("second", undefined) becomes necessary due to needing to pass two parameters.

Any suggestions or thoughts on how to tackle this issue would be greatly appreciated.

Answer №1

To proceed in a situation where the compiler needs to determine the signature of dispatch() programmatically based on the type of actions, some assumptions and adjustments need to be made for compilation purposes:

// uncertain types
type Context = { c: string };
declare function doSomething(c: Context, payload: string): number;
declare function doSomethingWithNoPayload(c: Context): boolean;

const actions = {
  first(context: Context, payload: string) { return doSomething(context, payload); },
  second(context: Context) { return doSomethingWithNoPayload(context); }
}

type Actions = typeof actions;

In TypeScript 3.0, support was introduced for using tuples to represent function parameter lists, enabling utilities like the Parameters<FuncType> type alias. Additionally, more manipulation capabilities for tuples were provided. For instance, defining Tail<T> that returns a new tuple with the first element removed:

// remove the first element from a tuple
// e.g., Tail<[1,2,3]> is [2,3]
type Tail<T extends readonly any[]> =
  ((...t: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never;

Following this, a single signature for dispatch() can be created. It takes one argument of type A and a rest parameter of type

Tail<Parameters<Actions[A]>>
.

// utilize rest tuples
declare function dispatch<A extends keyof Actions>(
  action: A, ...payload: Tail<Parameters<Actions[A]>>
): ReturnType<Actions[A]>;

This approach should provide the desired behavior:

dispatch("first") // error, no payload specified
dispatch("first", false) // error, incorrect payload type
dispatch("first", "correct") // ok, correct payload type

dispatch("second", "something") // error, payload not expected
dispatch("second") // ok, no payload passed
dispatch("third") // error, action does not exist

These modifications should align with your requirements. Best of luck as you implement them!

Link to code

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

Set the values retrieved from the http get response as variables in an Angular application

Lately, I've been working on a settings application with slide toggles. Currently, I have set up local storage to store the toggle state. However, I now want to update the toggle status based on the server response. The goal is to toggle buttons accor ...

Exploring the capabilities of batch updates in Firestore with the latest web version-9

I am facing issues when trying to update certain values in my firestore collection within my nuxt project. const batch = writeBatch(firestore); const data = query(collection(firestore, `notifications`, `${uid}/news`)); const querySnapshot = await ...

Element in Vue not displaying when modal is hidden

Having trouble displaying a Vue component on a modal controlled by semantic ui. The Vue element is not loading or mounting properly. My setup involves Laravel and jQuery with some Vue elements intertwined. I have a hunch that the issue stems from the moda ...

Be cautious of potential warnings and errors that may arise while utilizing casl and vue together

I've been attempting to integrate @casl/ability and @casl/vue into my VueJS application, but I'm facing difficulties making it work. I'm unsure if I'm making a mistake in the implementation or if there is a compatibility issue with the ...

Is there a way to expand the return type of a parent class's methods using an object

Currently, I am enhancing a class by adding a serialize method. My goal is for this new method to perform the same functionality as its parent class but with some additional keys added. export declare class Parent { serialize(): { x: number; ...

Encountering an issue with Angular 13 routing where extraction of property fails when returning value as an observable

After receiving an id number from the parent component, I pass it to my child component in order to retrieve a value with multiple properties. To achieve this, I created a service that returns an observable containing the desired object. However, when atte ...

When faced with the error message "Typescript property does not exist on union type" it becomes difficult to assess the variable

This question is a continuation of the previous discussion on Typescript property does not exist on union type. One solution suggested was to utilize the in operator to evaluate objects within the union. Here's an example: type Obj1 = { message: stri ...

When integrating Material-UI with REDUX, I am experiencing difficulties in retrieving the most recent value from the global state

Is there a way to update the isAuthenticated state when Material-UI code is involved? I need the Navbar to display different options based on whether the user is authenticated or not. import React from "react"; import clsx from "clsx"; import { withStyles ...

How can radios be made reusable in Vue.js?

Recently started delving into vue.js and encountered an issue where radio values were being repeated in the v-for div. Any ideas on how to resolve this problem? Check out this JSFiddle for reference. <script src="https://unpkg.com/<a href="/cdn-cgi ...

The error message "Property 'DecalGeometry' is not found in the type 'typeof "..node_modules/@types/three/index"'."

Within my Angular6 application, I am utilizing 'three.js' and 'three-decal-geometry'. Here is a snippet of the necessary imports: import * as THREE from 'three'; import * as OBJLoader from 'three-obj-loader'; import ...

Challenges in Power BI Custom Visual Development: Trouble setting height for div elements

Currently, I am working on creating a custom visual for Power BI that includes a leaflet map within a div element. However, the issue arises when I fail to set a specific height for the map, resulting in an empty visual. I have managed to set a fixed heigh ...

Cannot execute loop

I'm currently working on creating a loop within my component that involves making server calls: getBeds() { this.patientService.getBeds(this.selectedWard).subscribe( result => { console.log(result); this.beds = result; this.getBedDet ...

Ensuring Image Loading in Vue Next: Tips for Verification

I am currently exploring how to determine if an image is loading in my project. I am using vite along with Vue Next (v3), however, I have not been able to find much information on this topic for Vue Next. I attempted using @load on the img element, but it ...

Ensure that the query value remains constant in Express.js

Issue: The query value is changing unexpectedly. // url: "http://localhost:4000/sr?q=%C3%BCt%C3%BC" export const search = async (req: Request, res: Response) => { try { const query = String(req.query.q) console.log("query: &quo ...

Tips for securely duplicating an item from the state to prevent the view from altering the store directly

On my profile page, I wanted to allow users to update their information. Initially, I fetched the data from the store using: this.$store.state.auth.user However, when I tried binding this data to input controls using v-model, the <input> element wo ...

Incorporate a personalized Cypress function for TypeScript implementation

I'm in the process of developing a custom cypress command that will enable me to post a file using formData, as the current cy.request does not yet support formData. For the actual POST operation, I am utilizing request-promise-native. To begin with ...

What is the best way to showcase a firebase "row" containing two columns within an Ionic 2 application?

Currently in the process of developing an app to keep track of assignments using Ionic 2/Typescript and Firebase as the backend database. The main page displays a list of assignments retrieved from the database. Creating a new assignment requires the user ...

Implementing Material Design in React using TypeScript

I'm currently in search of a method to implement react material design with typescript, but I've encountered some challenges. It appears that using export defaults may not be the best practice due to constraints in my tsconfig. Is there an al ...

Can you explain the significance of the "type" reserved word in TypeScript?

When attempting to construct an interface in TypeScript, I discovered that the word "type" is both a keyword and a reserved term. In Visual Studio 2013 with TypeScript 1.4, for instance, when defining the following interface: interface IExampleInterface { ...

Passing an HTML5 video element as a prop in Vue.js

I am attempting to pass an HTML5 video as a property from a parent component to a child component in Vuejs. Parent Component: <template> <div> <video ref="video"> <source src="@/assets/video.mp4" type= ...