-
-
Notifications
You must be signed in to change notification settings - Fork 32.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
[typescript] Fix with* injectors ignoring defaultProps #12673
Conversation
efefed7
to
0c7073d
Compare
Thought reopening triggers CI which is not the case. Rebased with master to trigger CI. Failures where unrelated. |
); | ||
} | ||
}, | ||
); |
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.
There's already a test for union props in stylingComparison.spec.tsx
.
// https://github.com/mui-org/material-ui/issues/12670 | ||
interface Props { | ||
noDefault: string; | ||
withDefaultProps: number; |
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.
Can we call these props nonDefaulted
and defaulted
respectively?
packages/material-ui/src/index.d.ts
Outdated
*/ | ||
export type Shared< | ||
InjectedProps, | ||
DecorationTargetProps extends Shared<InjectedProps, DecorationTargetProps> |
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.
- It's confusing that
InjectedProps
andDecorationTargetProps
are in the opposite order here vs. inMatching
. - This self-referential constraint is bending my mind:
DecorationTargetProps extends Shared<InjectedProps, DecorationTargetProps>
. What's the rationale for this? Removing it doesn't break any tests, which makes me suspect it's extraneous.
Omit< | ||
JSX.LibraryManagedAttributes<C, PropsOf<C>>, | ||
keyof Shared<WithStyles<ClassKey, Options['withTheme']>, PropsOf<C>> | ||
> & |
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.
We only ever use keyof Shared<...>
? This makes me think we can get rid of Shared
completely and just use
Omit<
JSX.LibraryManagedAttributes<C, PropsOf<C>>,
(keyof WithStyles<ClassKey, Options['withTheme']>) & (keyof PropsOf<C>)
>
here. Furthermore the & (keyof PropsOf<C>)
seems redundant, and it can just be
Omit<
JSX.LibraryManagedAttributes<C, PropsOf<C>>,
keyof WithStyles<ClassKey, Options['withTheme']>
>
packages/material-ui/src/index.d.ts
Outdated
[P in keyof DecorationTargetProps]: P extends keyof InjectedProps | ||
? InjectedProps[P] extends DecorationTargetProps[P] ? DecorationTargetProps[P] : never | ||
: DecorationTargetProps[P] | ||
}; |
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.
It seems like Matching
is meant to accomplish much the same as ConsistentWith
, but maybe does a better job? Do we still need ConsistentWith
or can Matching
replace it everywhere?
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 thought so too but ConsistentWith
did not help with this issue. Maybe replace ConsistentWith
in a future PR with Matching
or explore where they actually differ. Same goes for AnyComponent
.
@pelotom The requested changes made the definitions much easier to follow. Thank you. |
|
||
class Component extends React.Component<Props & WithStyles<typeof styles>> {} | ||
// $ExpectError | ||
const StyledComponent = withStyles(styles)(Component); |
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.
This consistency check isn't working when applied to an SFC:
// $ExpectError
withStyles(styles)((props: Props) => null);
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.
@pelotom This is a limitation of the never
type. I cannot assign T to never but a function that takes T to a function that takes never. typescript playground
If I type this function explicitly as a React.SFC
I do get errors but the message is confusing:
Property 'children' is missing in type 'ValidationMap<Props>'
.
I'm going to verify if this was working before. If we can't resolve this we have to make a decision whether we want to support defaultProps
or the consistency check for stateless components.
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.
It did indeed work.
I would prefer defaultProps
support over consistency checks for implicit SFCs. We could always add a section to the typescript guide.
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.
Hm, are you 100% sure that we can't support both? I haven't looked at this in enough detail to know... but I agree that defaultProps
takes precedence if it has to be one or the other.
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.
Not with the never
approach. That's either an issue with ts (although I couldn't really find reports on this) or an actual limitation. I tried using the previous approach with ConsistentWith but either ts inferred {}
as the prop type or I got errors when passing a styled component to withTheme
I probably revisit the react-redux typings since I used the Matching
from there and see if I can connect inconsistent props with their typings too. That way we get more focus on the issue and maybe get some more help.
But as is the issue with #12697 this problem at least has a workaround with explicit typing.
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.
If we can't do a complete job of enforcing consistency maybe we should just remove the constraints completely, as they significantly complicate the expression of the types, and arguably they're of fairly niche value.
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.
@eps1lon as the original author on the react-redux PR, happy to help out if I can. I haven't gotten any reviews in 20 days so it's honestly been a bit hard to iterate/improve on the definition. If there are edge cases it doesn't handle, I can certainly iterate.
I also have my email in my github profile so feel free to reach out that way.
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.
FWIW, is the issue you're dealing with related to this by any chance DefinitelyTyped/DefinitelyTyped#28249?
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.
That issue is probably causing the fail with the explicitly typed SFC. So if that issue is resolved we might not even have a consistency check for explicitly typed SFCs.
Got it to work with a little gotcha that was already happening before. Before this change we returned a component that had an optional The issue is basically that the |
17dc556
to
5da60a0
Compare
This is great! Now it seems like we can define export type ConsistentWith<T, U> = Matching<U, T>; and everything works. So why not just redefine |
I'll take a look into that. I'm also looking at the other injector helpers
(`withTheme` etc.).
…On Thu, Sep 6, 2018, 19:43 Tom Crockett ***@***.***> wrote:
This is great! Now it seems like we can define
export type ConsistentWith<T, U> = Matching<U, T>;
and everything works. So why not just redefine ConsistentWith and get rid
of Matching?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#12673 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ALuPz81itbysbk-C_q9u5GuXE_xjBqCkks5uYV62gaJpZM4WN32V>
.
|
packages/material-ui/src/index.d.ts
Outdated
*/ | ||
export type Matching<InjectedProps, DecorationTargetProps> = { | ||
[P in keyof DecorationTargetProps]: P extends keyof InjectedProps | ||
? InjectedProps[P] |
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.
This no longer validates that the two interfaces passed in are matching. If InjectedProps[P]
doesn't extend DecorationTargetProps[P]
(assuming their keys are shared) then that means that the two definitions are incompatible and cannot be reconciled. E.g., this would accept: Matching<{ test: string; }, { test: boolean; }>
Edit: actually, you can ignore me, I think I see what you're doing? Maybe... Need to test it locally.
Edit2: Nope, this is definitely incorrect but not for the reason I mentioned. The problem is that if your component declares something as optional and the InjectedProps spec something as required then it won't match.
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.
@apapirovski
Which is sufficient for usage in withStyles
. The previous version allowed mismatch of props for functional components that did not have the React.SFC
type or to be more specific only had a call signature. I'm going to checkout the react/redux typings with your PR and see if this happens there to.
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 posted a new definition for Matching in my PR that is IMO slightly more correct, at least as far as connect
goes. Not sure if you have different requirements here as I don't use material-ui. Thanks for the test case and the triage!
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.
@apapirovski
I agree that approach is indeed better and I tried this before but this caused some regression. I realized that this was not an issue with your definition but with ours. Basically injecting T | undefined
while we actually only inject T
if it is defined which is different when using spread syntax.
@pelotom We can batch this for v4 since |
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.
Again this is a breaking change because it does not allow automatic type inference of the props.
Just to make sure I'm clear on this, you mean the fact that you can no longer write
withStyles(styles)<Props>(props => ...);
and instead have to write
withStyles(styles)((props: Props) => ...);
?
This all looks good to me. I would defer to @oliviertassinari about whether to wait for v4 to release this. In the past we have on occasion made backwards incompatible changes to the typings without revving a major version 🤷♂️ |
Yes. The changed tests illustrate theses changes.
…On Tue, Sep 11, 2018, 18:00 Tom Crockett ***@***.***> wrote:
***@***.**** commented on this pull request.
Again this is a breaking change because it does not allow automatic type
inference of the props.
Just to make sure I'm clear on this, you mean the fact that you can no
longer write
withStyles(styles)<Props>(props => ...);
and instead have to write
withStyles(styles)((props: Props) => ...);
?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#12673 (review)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ALuPz-y-Hbvl0WJ9wjA2fZJB4mLJ_ZHMks5uZ94ngaJpZM4WN32V>
.
|
It would be great to add a breaking change summary in the pull request description. If it's breaking a popular pattern, I think that we should wait v4. If its an edge case, I think that releasing it in the next minor is "OK".
Makes me believe people are already used to work around the problem. |
My guess is that we'd be breaking a fairly niche use case, and it's pretty straightforward to fix. Meanwhile, the upside of being able to use |
Thanks for all your hard work @eps1lon! |
Will be released in v3.1.0. |
* [core] Fix withStyles ignoring defaultProps * [typescript] Fix chore review issues * [typescript] Normalize order of InjectedProps and TargetProps * [typescript] Remove extraneous type constraint in Shared * [typescript] Removed extraneous Shared type * [typescript] Add more test cases for stateless componets * [typescript] Fix false positive consistency check for implicit SFCs * [typescript] Removed unused prop * [typescript] Cleanup component tests * [typescript] Fix wrong injected theme type in withStyles * [typescript] Introduce generic prop injector HOC
withStyles
,withWidth
andwithTheme
now preservesdefaultProps
information.The definitions are provided by DefinitelyTyped/DefinitelyTyped#28189.
This is a breaking change for typescript users that provided type arguments for the injectors or called the injectors with class or function expressions rather than with previously declared (and typed) variables. Looking at the use cases I had to change I think this is ok. The guides never used those patterns and this enforces a (in my opinion) cleaner component declaration
AnyComponent
was removed since it was no longer used.PropsOf
can't userRect.ComponentType
for inference though since this breaks union types.Fixes: #12670