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

What is the best way to put into practice a Calendar system?

What is the best way to integrate a Calendar in Angular2 using typescript? Are there any specific directives that need to be imported? ...

Is there a way to use the redux-persist API to automatically synchronize changes in localStorage with redux-persist?

I am facing an issue with my Redux application that utilizes redux-persist to store the auth JWT token. The app is supposed to retrieve the token from the redux store and place it in the HTTP Authorization header. The challenge arises when I log in as a d ...

Learn how to effectively showcase various components by leveraging the new react-router-dom v6.0.0 alongside react-redux

My issue is that when I click on a link to render different components, the URL updates but the UI remains unchanged. No matter which item I click on to render, the same thing happens. I've tried numerous solutions to fix this problem without success. ...

Locate every instance where two arrays are compared in TypeScript

My goal is to search for matches in Object 2 where the _type corresponds to filterByCallTypeTitulo in Object 1, and then create a new array including all the matched information from Object 2. I attempted to achieve this using the filter() method and forE ...

Using Vuejs to display errors with alerts

Is there a way to display errors using alerts in bootstrap when working with vuejs? This is an example of the code: <div v-if="this.getError"> <div v-for="(_errors, key) in this.getError"> <p>{{key.repla ...

What about combining a fat arrow function with a nested decorator?

Trying to implement a fat arrow function with a nestjs decorator in a controller. Can it be done in the following way : @Controller() export class AppController { @Get() findAll = (): string => 'This is coming from a fat arrow !'; } Wh ...

Property '{}' is not defined in type - Angular version 9.1.1

Currently, I am working with Angular CLI version 9.1.1 and I am attempting to update certain data without updating all of it. form: UserInfo = {adresse : {}}; UserInfo.interface export interface UserInfo { id_user: string; username: string; em ...

Using Vue.js to create a bidirectional binding between two input fields

I'm diving into vue.js for the first time and I've come across an issue. I want to create a scenario where C = A - B, and B = A - C. A is constant and any change in B or C should affect the other. While I successfully bound C using v-model and ...

Associate union with interface attributes

Can a union type be transformed into an interface in Typescript? My Desired Outcome If we have a union type A: type A = 'one' | 'two' | 'three'; I want to convert it to interface B: interface B { one: boolean; two ...

The synergy between JSDoc and type mapping

I am in search of a comprehensive and reliable source that provides detailed information on how TypeScript's JSDoc interacts with types, their mappings, and modifications. It is widely known that Pick and Omit maintain the original JSDoc: const any: ...

Subscribing to ngrx store triggers multiple emissions

Currently, I have an app with a ngrx store set up. I am experiencing an issue where, upon clicking a button, the function that fetches data from the store returns multiple copies of the data. Upon subsequent clicks, the number of returned copies grows expo ...

Different categories of "areas" found in TypeScript within Visual Studio 2013

In C#, we often use "regions," but unfortunately that feature is missing in TypeScript. Is there a way to group code sections in TypeScript? I came across this article on Stack Overflow discussing the absence of regions in TypeScript. I'm curious if ...

What is the best way to implement a late-binding clone method in TypeScript classes?

Creating a simple Cloneable interface for all my data classes in JavaScript is a straightforward task. However, when it comes to typing it properly in TypeScript, things get a bit more complex. Currently, I am putting together a solution like this: class ...

Explore the functionalities of Pinia Vue for querying data with a filter

My objective is to attract customers based on the groups ID that has been provided. Here is the current status of the customers: [ { "id": 1, "name": "Customer 1", "groups": [ { "id& ...

What is causing this issue in TypeScript version 4.8?

After updating to TypeScript 4.8 in VSCode, I have encountered an error in one of my React projects that was not present before. Strangely, this error does not prevent the code from compiling successfully when building the project. It's challenging to ...

The integration of Tinymce and Vuetify dialog is causing issues where users are unable to input text in the source code editor or add code samples

Having an issue with the Vuetify dialog and TinyMCE editor. Upon opening the dialog with the editor inside, certain functionalities like Edit source code or Insert code sample are not working as intended. Specifically, when attempting to use one of these p ...

Is it possible to integrate payment methods such as PayPal or Stripe in Vue.js without using a server like Express? If so, how can I implement this

After completing the development of my web shop in Vue.js, I realized that the payment method is still missing. I am wondering if I need to integrate Express in order to process payments through Stripe? Currently, I do not have a server like Express set up ...

Does vee validate only apply to the initial field?

I've gone ahead and created two modals, one for logging in (loginModal) and another for registering (registerModal). Users have the ability to seamlessly switch between the two. However, I've encountered an issue with the "VeeValidate" plugin. I ...

The exportAs property for matAutocomplete has not been specified

Issue Detected An error occurred with the directive "exportAs" set to "matAutocomplete" ("-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto"> I implemented code referenced from https://material.angular.io/components/auto ...

Show a visual representation when Blob is retrieved from an API call

Currently, I am working on a Vue app that integrates with the Microsoft Graph API and SDK for authentication at the front end, along with using various features of the API like displaying emails, OneDrive files, etc. One specific challenge I am facing is ...