default destructuring named parameters object optional automatically
When using "named parameters", if a parameter has a default (provided when destructuring), TypeScript should automatically mark that named parameter as optional. Example:
const myFn = ({ foo, bar = 'default' }: { foo: number; bar: string; }) => {
bar
}
// No error since `bar` is marked as automatically optional.
// (Currently this errors because `bar` is still marked as required.)
myFn({ foo: 1 });
You might ask "why not just manually mark the parameter as optional?" Good question! Because:
// We can't easily modify `SomeThirdPartyObject` here to mark `bar` as optional.
const myFn = ({ foo, bar = 'default' }: SomeThirdPartyObject) => {}
// Workaround
const myFn = ({ foo, bar = 'default' }: Omit<SomeThirdPartyObject, 'bar'> & Partial<Pick<SomeThirdPartyObject, 'bar'>>) => {}
myFn({ foo: 1 })
React function components are a very common use case for "named parameters" and destructuring with defaults.
import * as React from 'react';
type Props = { foo: number; bar: string; };
const MyComponent: React.FunctionComponent<Props> = ({
foo,
bar = 'default',
}) => null;
// No error since `bar` is marked as automatically optional.
// (Currently this errors because `bar` is still marked as required.)
<MyComponent foo={1} />;
const MyComponent = ({
foo,
bar = 'default',
}: Props) => null;
// No error since `bar` is marked as automatically optional.
// (Currently this errors because `bar` is still marked as required.)
<MyComponent foo={1} />;
(defaultProps is supposed to solve this exact problem, but it doesn't currently work for function components: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30695).
My suggestion meets these guidelines:
I have some hangups about this. I routinely check my required parameters. Just because TypeScript says it is required doesn't mean that constraint is respected on the JavaScrpt side. I personally love the idea of forcing required parameters and providing defaults (along with some more logic) to protect on the JS consumer side. I think making people explicitly state what they want serves the two thoughts fine as is. Also, you are talking about overriding the intended behaviour of a third-party design object. Is that wise?
I appreciate the symmetry with parameter defaults this would create, but I have probably a dozen objections to this.
What if you didn't want the property to be optional, i.e. you want an explicit undefined from the consumer? There'd be no way to opt out.
This is problematic for .d.ts generation when the type annotation isn't an anonymous object type.
As a first principle, if the user writes a type annotation, we shouldn't defy that annotation without some extremely good motivation.
There's an argument that destructuring default is just sugar for an additional conditional assignment at the top of the function body, which would imply the parameter type shouldn't change, though again this isn't preserved by the bare parameter default behavior.
We have the same problem, especially for factory constructors. We came with this workaround. Say we have the interface Message:
export interface Message {
fieldName?: string;
status: string;
statusText: string;
type: "ERROR" | "SUCCESS" | "WARNING" | "REDIRECT";
userMessage: string;
userTitle: string;
}
And you have a factory constructor, that just provides some defaults:
export function createMessage({
userMessage = '',
userTitle = '',
status = '',
statusText = '',
...rest
}: OptionalKeys<
Message,
'userMessage' | 'userTitle' | 'status' | 'statusText'
>): Message {
return {
userTitle,
userMessage,
type,
status,
statusText,
...rest,
};
}
We use this OptionalKeys type utility, to make sure the default values are marked as optional:
export type OptionalKeys<T extends object, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
Most helpful comment
I appreciate the symmetry with parameter defaults this would create, but I have probably a dozen objections to this.
What if you didn't want the property to be optional, i.e. you want an explicit
undefinedfrom the consumer? There'd be no way to opt out.This is problematic for .d.ts generation when the type annotation isn't an anonymous object type.
As a first principle, if the user writes a type annotation, we shouldn't defy that annotation without some extremely good motivation.
There's an argument that destructuring default is just sugar for an additional conditional assignment at the top of the function body, which would imply the parameter type shouldn't change, though again this isn't preserved by the bare parameter default behavior.