97

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...

3 Answers 3

141

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();

Sign up to request clarification or add additional context in comments.

3 Comments

Is there a way to avoid that duplication of opt1 and opt2 in the parameter object? Something like function myFunction({ opt1?: boolean; opt2?: boolean = false; } = {}).
@icl7126 unfortunately there isn't. Usually I will extract out the inline type to an interface in these cases because it's so verbose :(
14

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);
}

1 Comment

This answer was the only one that solved the problem for me (working with function overloads)
0

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}`)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.