Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript: How to make an optional second argument required based on the value of the first argument

Say I have a function that takes 2 args, and depending on the value of the first arg, the second arg may or may not be required.

For Example:

function calculate(item: 'icon' | 'category', state: IState): void {

    if (arg1 === 'icon') {
        // code in which I don't need to access anything in state
    } 

    if (arg1 === 'category') {
        // code in which I do need to access state
    }

}

If I were to run this as is, I would get an error if I write

calculate('icon') // will cause an error

This will also throw an error because I am not passing a valid value for the second arg

calculate('icon', null) // will also cause an error

In order to not get any errors, I have to call it like this

calculate('icon', this.state) // acceptable as long as 'this.state' meets the IState definition

I want to be able to call the function without passing the second argument if the first argument = 'icon'. Like this

calculate('icon') // should not cause an error

However, if I call calculate like this it should cause an error

calculate('category') // should cause an error

Any help would be greatly appreciated!

like image 908
Colin Knebl Avatar asked Mar 14 '19 23:03

Colin Knebl


2 Answers

You can use multiple overloads:

function calculate(item: 'icon'): void
function calculate(item: 'category', state: IState): void
function calculate(item: 'icon' | 'category', state?: IState): void {

    if (item === 'icon') {
        // code in which I don't need to access anything in state
    } 

    if (item === 'category' && state) {
        // code in which I do need to access state
    }

}
calculate("icon")
calculate("category") //error
like image 158
Titian Cernicova-Dragomir Avatar answered Oct 25 '22 23:10

Titian Cernicova-Dragomir


This is the solution I ended up going with. If anyone knows of a better way please share!

type ConditionalOptions<T, K extends keyof T> = T[K] extends null ? [] : [T[K]];
interface IPossibleValues {
    icon: {
        state: IState;
    };
    theme: null;
    category: {
        state: IState;
        cardItem: any;
    };
}

calculate = <T extends keyof IPossibleValues>(
    type: T,
    ...options: ConditionalOptions<IPossibleValues, T>
): string => {
    // code here
}

if this method is used, the value of each key in the interface IPossibleValues is what needs to be passed in to calculate as the second arg. Examples:

calculate('icon') // this will cause an error since no 'state' is passed in
calculate('icon', { state: stateVal }) // this will not generate a type error

calculate('theme') // this will not cause any type errors
calculate('theme', someValue) // this will cause an error

calculate('category', {
    state: stateVal,
    cardItem: cardVal,
}) // this will not cause an error
calculate('category', { state: stateVal }) // this will cause an error because 'cardItem' was not passed in

like image 29
Colin Knebl Avatar answered Oct 25 '22 23:10

Colin Knebl