Skip to content
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] Enable generic props for certain components #13868

Merged
merged 60 commits into from
Feb 10, 2019

Conversation

pelotom
Copy link
Member

@pelotom pelotom commented Dec 10, 2018

This revives the effort started in #11731, which ran into difficulty with callback parameter types no longer being inferred, thus breaking backwards compatibility. This new approach circumvents the issue by using overloads; in the simple case where no override component is provided, callback parameter types are fully inferred with no extra work. In the case where an override component is provided, you can either

  • provide the component type also as a generic parameter:
    <Button<'div'>
      component="div"
      onClick={e => { /* e is inferred as MouseEvent<HTMLDivElement> */ }}
    />
  • provide an explicit type for the callback parameter which agrees with the component prop:
    <Button
      component="div"
      onClick={(e: MouseEvent<HTMLDivElement>) => {}}
    />

So far in this PR only a handful of components have been converted to use this new typing strategy:

  • Avatar
  • Button
  • Fab
  • ListItem
  • Tabs

The results seem promising, but I wanted to solicit feedback before diving in too much deeper. One unfortunate issue is that error messages can be somewhat cryptic; for instance with

const Override = (props: {}) => <div />;

<Avatar component={Override} onClick={e => {}} />;

The error we get is that Property 'component' does not exist on type .... There is an error here, which is that we are providing a prop, onClick, which does not exist on the overriding component, however the message doesn't help us understand that.

Upgraded TypeScript to 3.2 because it contains a bugfix for microsoft/TypeScript#26004 which is necessary to make this work.

@pelotom pelotom requested a review from eps1lon December 10, 2018 01:41
@pelotom pelotom changed the title Typescript generics (new approach) Generic components (new approach) Dec 10, 2018
@pelotom pelotom changed the title Generic components (new approach) [typescript] Generic components (new approach) Dec 10, 2018
@@ -14,7 +14,8 @@ import TextField from '@material-ui/core/TextField';
variant="standard"
InputProps={{
// notchedOutline is only used with variant "outlined"
classes: { inputTypeSearch: 'search-input', notchedOutline: 'notched-outline' }, // $ExpectError
// FIXME this no longer generates an error in TS 3.2, see https://github.com/Microsoft/TypeScript/issues/28926
// classes: { inputTypeSearch: 'search-input', notchedOutline: 'notched-outline' }, // $ExpectError
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upgrading TypeScript broke this test, due to microsoft/TypeScript#28926.

@eps1lon
Copy link
Member

eps1lon commented Feb 5, 2019

@pelotom I did commit some changes from my own review that I thought weren't that controversial. This includes some code comments for the types.

Could you go over some unaddressed review comments? I think we can start converting the other declarations and get some feedback on a alpha release.

@eps1lon eps1lon changed the title [typescript] Generic components (new approach) [typescript] Enable generic props for certain components Feb 8, 2019
@pelotom
Copy link
Member Author

pelotom commented Feb 8, 2019

@eps1lon very sorry I've been AWOL on this, work has been crazy. I should have time to look this over tonight.

@eps1lon
Copy link
Member

eps1lon commented Feb 8, 2019

@eps1lon very sorry I've been AWOL on this, work has been crazy. I should have time to look this over tonight.

No worries. I think I only fixed some nitpicks I had. Looks like we can start applying this to the rest of the components which hopefully is pretty straight forward and doesn't break any tests.

@pelotom
Copy link
Member Author

pelotom commented Feb 9, 2019

Looks like we can start applying this to the rest of the components which hopefully is pretty straight forward and doesn't break any tests.

I assume you mean merge this and then start to gradually migrate other components to the new system, yeah?

@@ -163,7 +163,7 @@ const BottomNavigationTest = () => {
const value = 123;

return (
<BottomNavigation value={value} onChange={e => log(e)} showLabels>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of writing these as lambdas was to ensure that the argument type was being inferred...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it complain that log is not assignable to (e: EventType) => ReturnType in those cases? What makes this different from the cases where you added an explicit ExpectType?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it complain that log is not assignable to (e: EventType) => ReturnType in those cases?

Perhaps it should, but it doesn't.

What makes this different from the cases where you added an explicit ExpectType?

It's a less rigorous test (just that something other than any was inferred for the argument, not making an assertion about what was inferred). There's no reason an ExpectType couldn't be added also.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you quickly push a test that is not throwing when it should for my own peace of mind? Just want to make sure we're not disabling some lint rule that was removed because of something unrelated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't think I follow. Could you elaborate?

Copy link
Member Author

@pelotom pelotom Feb 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn’t test inference because you’re passing a function whose signature is already determined. The point is to pass an inline function with no parameter signature so that if it can’t infer the type there will be a noImplicitAny error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is to pass an inline function with no parameter signature so that if it can’t infer the type there will be a no-implicit-any error.

Ah now I understand. Happy to revert a7f6d4c then.

Copy link
Member Author

@pelotom pelotom Feb 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we didn’t want to disable the lint rule we could just as well pass e => {} to test this, the log call is unimportant... but IIRC the only reason for it was to appease another lint rule for unused variables.

Probably should just selectively disable the lint rule for these lines.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with disabling the rule since our ts files never make it to runtime anyway. I would say that the only useful pattern the lint rule flags is in react props. A new callback could defeat memoized components.

The ts files in our docs are transpiled to JS at which point eslint would cover that case.

If end up disabling the rules for every instance of unnecessary callbacks then we might as well disable it globally (or maybe starting with per file disables)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I reverted 79c4872.

@pelotom pelotom merged commit 54f9a33 into mui:next Feb 10, 2019
@pelotom pelotom deleted the typescript-generics-new-approach branch February 10, 2019 05:30
caroe233 pushed a commit to caroe233/material-ui that referenced this pull request Mar 21, 2019
@caroe233 caroe233 mentioned this pull request Mar 22, 2019
1 task
eps1lon pushed a commit that referenced this pull request Mar 23, 2019
* Changed the Tab component to work with generic props (see #13868)

* successfully run yarn prettier with the correct config

* successfully ran yarn typescript
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants