-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Support LibraryManagedAttributes<TComponent, TAttributes> JSX namespace type #24422
Conversation
Could you elaborate in this PR on what |
& {[K in Exclude<keyof TProps, keyof TDefaults>]: TProps[K]} | ||
& Partial<TDefaults>; | ||
|
||
type InferedPropTypes<P> = {[K in keyof P]: P[K] extends PropTypeChecker<infer T, infer U> ? PropTypeChecker<T, U>[typeof checkedType] : {}}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's just a test, but InferedPropTypes
-> InferredPropTypes
const x = <JustDefaultPropsWithSpecifiedGeneric foo="eh" />; | ||
const y = <JustDefaultPropsWithSpecifiedGeneric foo="no" bar="ok" />; // error, no prop named bar | ||
const z = <JustDefaultPropsWithSpecifiedGeneric foo={12} />; // error, wrong type | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about <JustDefaultPropsWithSpecifiedGeneric />
? Is that kosher?
As background, TS works differently than Flow at the moment with React Beforeinterface Props {
thing?: string; // thing is optional, but it is always defined because it has a default prop.
}
class Thing extends React.Component<Props> {
static defaultProps = {
thing: 'hello'
}
render() {
console.log(this.props.thing!.length) // forced with !
// ...
}
}
// ..
<Thing /> // this works Afterinterface Props {
thing?: string; // still optional
}
class Thing extends React.Component<Props> {
static defaultProps = {
thing: 'hello'
}
render() {
console.log(this.props.thing) // inferred because of defaultProps, no ! needed
// ...
}
}
// ..
<Thing /> // works |
@jaredpalmer The interface Props {
thing?: string; // still optional
}
class Thing extends React.Component<Props> {
static defaultProps = {
thing: 'hello'
}
render() {
console.log(this.props.thing) // inferred because of defaultProps, no ! needed
// ...
}
}
// ..
<Thing /> // works you'd have interface Props {
thing: string; // note: not optional within class, but is optional when writing due to the default
}
class Thing extends React.Component<Props> {
static defaultProps = {
thing: 'hello'
}
render() {
console.log(this.props.thing) // strict type from `Props`
// ...
}
}
// ..
<Thing /> // works, thanks to `defaultProps` or at least that's what makes sense to me - since the |
got it so it will work like Flow. cool! |
This is indeed awesome! In the meantime I wrote an article for React consumers struggling with this "deal breaker" ✌️ https://medium.com/@martin_hotell/react-typescript-and-defaultprops-dilemma-ca7f81c661c7 |
type Defaultize<TProps, TDefaults> = | ||
& {[K in Extract<keyof TProps, keyof TDefaults>]?: TProps[K]} | ||
& {[K in Exclude<keyof TProps, keyof TDefaults>]: TProps[K]} | ||
& Partial<TDefaults>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the Partial<TDefaults>
here intersected with the extracted mapped type from TDefaults
? What kind of error does that produce if typeof TProps[K]
is incompatible with typeof TDefaults[K]
?
@mhegazy I've synchronized this branch with |
What is the expected behaviour when the props type is a union? Is this expected? {
type Props = { shared: string } & (
| {
type: 'foo';
foo: string;
}
| {
type: 'bar';
bar: string;
});
class Component extends ReactComponent<Props> {
static defaultProps = {
shared: 'yay',
};
}
<Component type="foo" />; // Expected error, got none
// Unexpected error
// Property 'foo' does not exist on type 'Defaultize<Props, { shared: string; }>'.
<Component type="foo" foo="1" />;
} |
@OliverJAsh this is a different issue i am afraid. |
@mhegazy Is it an issue worth tracking? If so, I'll open a separate issue. |
yes please. |
@OliverJAsh it's probably also worth noting that while this code is in |
Locking so users open up new issues instead. That way, we can more easily follow up. |
Fixes #23812
Allows, eg:
when
JSX.LibraryManagedAttributes<TComponent, TAttributes>
is appropriately defined. Thanks to conditional types, this method of supportingpropTypes
anddefaultProps
allows definition authors a ton of freedom in how the static types need to be transformed before they are checked against what the user wrote. (Allowing customization of, for example: how conflicts between provided props and inferred props are handled, how inferences are mapped, how optionality is handled, and how inferences from differing places should be combined)As an aside, you can fully implement the functionality of both
InstrinsicAttributes
andInstrinsicClassAttributes
using this new type entrypoint, so in a way it obsoletes them.Feel free to 🚲 🏠 with the name and give feedback; the actual implementation is relatively simple. @DanielRosenwasser you probably have some opinions here, right? 😄