Can you have optional destructured arguments in a Typescript function?

Typescript

Typescript Problem Overview


I'd like to write a function that takes an object argument, uses destructuring in the function signature, and have that argument be optional:

myFunction({opt1, opt2}?: {opt1?: boolean, opt2?: boolean})

However, Typescript doesn't let me ("A binding pattern parameter cannot be optional in an implementation signature.").

Of course I could do it if I didn't destructure:

myFunction(options?: {opt1?: boolean, opt2?: boolean}) {
  const opt1 = options.opt1;
  const opt2 = options.opt1;
  ...

It seems like these should be the same thing, yet the top example is not allowed.

I'd like to use a destructured syntax (1) because it exists, and is a nice syntax, and it seems natural that the two functions above should act the same, and (2) because I also want a concise way to specify defaults:

myFunction({opt1, opt2 = true}?: {opt1?: boolean, opt2?: boolean})

Without destructuring, I have to bury these defaults in the implementation of the function, or have an argument that is actually some class with a constructor...

Typescript Solutions


Solution 1 - Typescript

Use a default parameter instead:

function myFunction({ opt1, opt2 = true }: { opt1?: boolean; opt2?: boolean; } = {}) {
    console.log(opt2);
}

myFunction(); // outputs: true

It's necessary in order to not destructure undefined:

function myFunction({ opt1, opt2 }) {
}
    
// Uncaught TypeError: Cannot destructure property `opt1` of 'undefined' or 'null'.
myFunction();

Solution 2 - Typescript

You can't destructure if there is not an object given as an argument. Therefore use a default object in the parmas as the previous post mentioned:

type Options = { opt1?: boolean; opt2?: boolean; }

function myFunction({ opt1, opt2 }: Options = {}) {
    console.log(opt2, opt1);
}

myFunction() // undefined,  undefined 
myFunction({opt1: false}); // undefined,  false 
myFunction({opt2: true}); // true,  undefined

What I would like to add is that this destructuring pattern in the params adds the most value when the following 2 conditions hold:

  • The amount of options is likely to change
  • There could potentially be change in API of the function. i.e. the function params are likely to change

Basically destructuring gives you more flexibility since you can add as many options as you like with minimal change to the function's API.

However, the more basic version would be simpler:

// If the function is not likely to change often just keep it basic:
function myFunctionBasic( opt1? :boolean, opt2?: boolean ) {
    console.log(opt2, opt1);
}

Solution 3 - Typescript

This is the best way if you want to mix optional and required arguments but have an exported type where all values are required / non-null:

export function newCar(args: {
    year?: number
    make?: string
    model: string
    owner: [string, string]
}) {
    const defaults = {
        year: 1999,
        make: "toyota",
    }
    return { ...defaults, ...args }
    // to get a Readonly<Car>:
    // return Object.freeze(args)
}

export type Car = ReturnType<typeof newCar>

const c = newCar({ model: "corolla", owner: ["Gorg", "Blafson"] })

export function print(c: Car) {
    console.log(`${c.owner}'s gorgeous ${c.year} ${c.model} from ${c.make}`)
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSam FenView Question on Stackoverflow
Solution 1 - TypescriptDavid SherretView Answer on Stackoverflow
Solution 2 - TypescriptWillem van der VeenView Answer on Stackoverflow
Solution 3 - TypescriptLuke MilesView Answer on Stackoverflow