Skip to content

Suggestion: automatically mark named parameters with defaults as optional #30488

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
4 of 5 tasks
OliverJAsh opened this issue Mar 19, 2019 · 3 comments
Closed
4 of 5 tasks
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@OliverJAsh
Copy link
Contributor

OliverJAsh commented Mar 19, 2019

Search Terms

default destructuring named parameters object optional automatically

Suggestion

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:

  • When modifying defaults, we must remember to manually modify the parameter types to match any defaults we have added/removed.
  • Sometimes the parameter types are not easily modifyable, e.g. when it comes from a third party (example below).
// 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 })

Use Cases

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: DefinitelyTyped/DefinitelyTyped#30695).

Examples

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@rjamesnw
Copy link

rjamesnw commented Mar 19, 2019

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?

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Declined The issue was declined as something which matches the TypeScript vision labels Mar 19, 2019
@RyanCavanaugh
Copy link
Member

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.

@kasperpeulen
Copy link

kasperpeulen commented Dec 16, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants