From e1f809dd49055d185c3624f1cab0f0585db9dd6c Mon Sep 17 00:00:00 2001 From: Ed Staub Date: Thu, 12 Apr 2018 09:33:54 -0400 Subject: [PATCH 01/16] Rewrite withStyles definition, using Omit My experience is that the current `withStyles` declaration works poorly with Typescript 2.8. I'm not sure that it's the compiler version that's the issue, though. At least in my usage, it fails to remove `WithStyles` from the return type. This definition works better in my experience, doesn't rely on subtle inferencer behavior in the same way, and seems to be in the mainstream of current usage. There's a built-in `Exclude` function in Typescript 2.8 that does what `Omit` does. I used `Omit` for compatibility with older compilers. --- src/styles/withStyles.d.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/styles/withStyles.d.ts b/src/styles/withStyles.d.ts index d5bd55cc59bc7f..a1e20ae1b6fd15 100644 --- a/src/styles/withStyles.d.ts +++ b/src/styles/withStyles.d.ts @@ -38,9 +38,13 @@ export interface StyledComponentProps { innerRef?: React.Ref; } +// Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 +export type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]; +export type Omit = Pick>; + export default function withStyles( - style: StyleRules | StyleRulesCallback, - options?: WithStylesOptions, -):

( - component: React.ComponentType

>, -) => React.ComponentType

>; + style: StyleRules | StyleRulesCallback, + options?: WithStylesOptions, +):

>( + component: React.ComponentType

, +) => React.ComponentType> & StyledComponentProps>; From f488adf7b15750f9246c1b5ca83f61afdaa77e04 Mon Sep 17 00:00:00 2001 From: estaub Date: Thu, 12 Apr 2018 15:11:46 -0400 Subject: [PATCH 02/16] Fix test issues; redesign withTheme, withWidth using Omit also. --- src/styles/withStyles.d.ts | 21 +++++++++++---------- src/styles/withTheme.d.ts | 7 ++++--- src/utils/withWidth.d.ts | 7 ++++--- test/typescript/components.spec.tsx | 2 +- test/typescript/styles.spec.tsx | 8 ++++---- test/typescript/styling-comparison.spec.tsx | 4 ++-- 6 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/styles/withStyles.d.ts b/src/styles/withStyles.d.ts index a1e20ae1b6fd15..5d340d3be03921 100644 --- a/src/styles/withStyles.d.ts +++ b/src/styles/withStyles.d.ts @@ -1,4 +1,6 @@ import * as React from 'react'; +import { WithTheme } from '../styles/withTheme'; +import { Omit } from '..'; import { Theme } from './createMuiTheme'; /** @@ -28,9 +30,8 @@ export interface WithStylesOptions { export type ClassNameMap = Record; -export interface WithStyles { +export interface WithStyles extends Partial { classes: ClassNameMap; - theme?: Theme; } export interface StyledComponentProps { @@ -38,13 +39,13 @@ export interface StyledComponentProps { innerRef?: React.Ref; } -// Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 -export type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]; -export type Omit = Pick>; - +/* + The Omit below (rather than the canonical Omit>) + is used to allow interoperability with withTheme, which may provide the 'theme' prop. + */ export default function withStyles( - style: StyleRules | StyleRulesCallback, - options?: WithStylesOptions, + style: StyleRules | StyleRulesCallback, + options?: WithStylesOptions, ):

>( - component: React.ComponentType

, -) => React.ComponentType> & StyledComponentProps>; + component: React.ComponentType

, +) => React.ComponentType & StyledComponentProps>; diff --git a/src/styles/withTheme.d.ts b/src/styles/withTheme.d.ts index ee01063b2512be..a0f14f0e075c6f 100644 --- a/src/styles/withTheme.d.ts +++ b/src/styles/withTheme.d.ts @@ -1,11 +1,12 @@ import { Theme } from './createMuiTheme'; +import { Omit } from '..'; export interface WithTheme { theme: Theme; } -declare const withTheme: () =>

( - component: React.ComponentType

, -) => React.ComponentClass

; +declare const withTheme: () =>

( + component: React.ComponentType

, +) => React.ComponentClass>; export default withTheme; diff --git a/src/utils/withWidth.d.ts b/src/utils/withWidth.d.ts index e031c3d41296ec..61f701cf6a4a8f 100644 --- a/src/utils/withWidth.d.ts +++ b/src/utils/withWidth.d.ts @@ -1,4 +1,5 @@ import { Breakpoint } from '../styles/createBreakpoints'; +import { Omit } from '..'; export interface WithWidthOptions { resizeInterval: number; @@ -22,6 +23,6 @@ export function isWidthUp( export default function withWidth( options?: WithWidthOptions, -):

( - component: React.ComponentType

, -) => React.ComponentClass

>; +):

( + component: React.ComponentType

, +) => React.ComponentClass & Partial>; diff --git a/test/typescript/components.spec.tsx b/test/typescript/components.spec.tsx index 851755d6d29ad0..8b472530b54681 100644 --- a/test/typescript/components.spec.tsx +++ b/test/typescript/components.spec.tsx @@ -718,7 +718,7 @@ const TableTest = () => { ); } - return withStyles(styles)<{}>(BasicTable); + return withStyles(styles)>(BasicTable); }; const TabsTest = () => { diff --git a/test/typescript/styles.spec.tsx b/test/typescript/styles.spec.tsx index 3e386e265bd670..44385a89ff9175 100644 --- a/test/typescript/styles.spec.tsx +++ b/test/typescript/styles.spec.tsx @@ -28,9 +28,9 @@ const styles: StyleRulesCallback<'root'> = ({ palette, spacing }) => ({ }, }); -const StyledExampleOne = withStyles(styles)(({ classes, text }) => ( -

{text}
-)); +const StyledExampleOne = withStyles(styles)>( + ({ classes, text }) =>
{text}
, +); ; // Example 2 @@ -57,7 +57,7 @@ const ComponentWithChildren: React.SFC> = ({ children, }) =>
{children}
; -const StyledExampleThree = withStyles(styleRule)<{}>(ComponentWithChildren); +const StyledExampleThree = withStyles(styleRule)>(ComponentWithChildren); ; // Also works with a plain object diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index 45ef738fdb105c..556539ee914fe9 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -16,7 +16,7 @@ interface Props { variant: TypographyProps['variant']; } -const DecoratedSFC = decorate(({ text, variant, color, classes }) => ( +const DecoratedSFC = decorate>(({ text, variant, color, classes }) => ( {text} @@ -35,7 +35,7 @@ const DecoratedClass = decorate( }, ); -const DecoratedNoProps = decorate<{}>( +const DecoratedNoProps = decorate>( class extends React.Component> { render() { return Hello, World!; From 3560ac0a82572c360fd24f02e630301006701ba9 Mon Sep 17 00:00:00 2001 From: estaub Date: Thu, 12 Apr 2018 21:04:09 -0400 Subject: [PATCH 03/16] Use preferred style in tests --- test/typescript/components.spec.tsx | 2 +- test/typescript/styles.spec.tsx | 6 ++++-- test/typescript/styling-comparison.spec.tsx | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/typescript/components.spec.tsx b/test/typescript/components.spec.tsx index 8b472530b54681..f0280f1163ba55 100644 --- a/test/typescript/components.spec.tsx +++ b/test/typescript/components.spec.tsx @@ -718,7 +718,7 @@ const TableTest = () => { ); } - return withStyles(styles)>(BasicTable); + return withStyles(styles)(BasicTable); }; const TabsTest = () => { diff --git a/test/typescript/styles.spec.tsx b/test/typescript/styles.spec.tsx index 44385a89ff9175..ca92ef011917c8 100644 --- a/test/typescript/styles.spec.tsx +++ b/test/typescript/styles.spec.tsx @@ -28,9 +28,11 @@ const styles: StyleRulesCallback<'root'> = ({ palette, spacing }) => ({ }, }); -const StyledExampleOne = withStyles(styles)>( - ({ classes, text }) =>
{text}
, +const StyledViewOne: React.SFC> = ({ classes, text }) => ( +
{text}
); +const StyledExampleOne = withStyles(styles)(StyledViewOne); + ; // Example 2 diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index 556539ee914fe9..6feb7dc8ac9b54 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -16,11 +16,12 @@ interface Props { variant: TypographyProps['variant']; } -const DecoratedSFC = decorate>(({ text, variant, color, classes }) => ( +const Decoratee: React.SFC> = ({ text, variant, color, classes }) => ( {text} -)); +); +const DecoratedSFC = decorate(Decoratee); const DecoratedClass = decorate( class extends React.Component> { @@ -35,7 +36,7 @@ const DecoratedClass = decorate( }, ); -const DecoratedNoProps = decorate>( +const DecoratedNoProps = decorate( class extends React.Component> { render() { return Hello, World!; From 8eb517aa805d56ddb96a3ca4e0cb706f079273a1 Mon Sep 17 00:00:00 2001 From: estaub Date: Fri, 13 Apr 2018 09:41:29 -0400 Subject: [PATCH 04/16] Remove another inferrable generic parameter from test. --- test/typescript/styles.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/typescript/styles.spec.tsx b/test/typescript/styles.spec.tsx index ca92ef011917c8..ee5c69327bc888 100644 --- a/test/typescript/styles.spec.tsx +++ b/test/typescript/styles.spec.tsx @@ -59,7 +59,7 @@ const ComponentWithChildren: React.SFC> = ({ children, }) =>
{children}
; -const StyledExampleThree = withStyles(styleRule)>(ComponentWithChildren); +const StyledExampleThree = withStyles(styleRule)(ComponentWithChildren); ; // Also works with a plain object From 175fa4944fc8f8e8bbc672e25a4a3ccc44000626 Mon Sep 17 00:00:00 2001 From: estaub Date: Sat, 14 Apr 2018 12:33:19 -0400 Subject: [PATCH 05/16] Remove another inferrable generic parameter from test. --- test/typescript/styles.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/typescript/styles.spec.tsx b/test/typescript/styles.spec.tsx index ee5c69327bc888..b501da079b31c2 100644 --- a/test/typescript/styles.spec.tsx +++ b/test/typescript/styles.spec.tsx @@ -138,7 +138,7 @@ function OverridesTheme() { ); } -// withTheme +// withTheme. const ComponentWithTheme = withTheme()(({ theme }) =>
{theme.spacing.unit}
); ; From 92bee1b4006eacfca816b9b297e6efef5b49301f Mon Sep 17 00:00:00 2001 From: estaub Date: Sat, 14 Apr 2018 16:41:09 -0400 Subject: [PATCH 06/16] Make inclusion of WithStyles in the innerComponent's type def optional. --- src/styles/withStyles.d.ts | 13 +++++++++++++ test/typescript/styles.spec.tsx | 8 +++----- test/typescript/styling-comparison.spec.tsx | 5 ++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/styles/withStyles.d.ts b/src/styles/withStyles.d.ts index 5d340d3be03921..b57fff46e7fa68 100644 --- a/src/styles/withStyles.d.ts +++ b/src/styles/withStyles.d.ts @@ -43,9 +43,22 @@ export interface StyledComponentProps { The Omit below (rather than the canonical Omit>) is used to allow interoperability with withTheme, which may provide the 'theme' prop. */ +/* export default function withStyles( style: StyleRules | StyleRulesCallback, options?: WithStylesOptions, ):

>( component: React.ComponentType

, ) => React.ComponentType & StyledComponentProps>; +*/ + +interface MaybeWithStyles extends Partial> { + [k: string]: any; +} + +export default function withStyles( + style: StyleRules | StyleRulesCallback, + options?: WithStylesOptions, +):

>( + component: React.ComponentType

>, +) => React.ComponentType & StyledComponentProps>; diff --git a/test/typescript/styles.spec.tsx b/test/typescript/styles.spec.tsx index b501da079b31c2..e4ecc69dcc4812 100644 --- a/test/typescript/styles.spec.tsx +++ b/test/typescript/styles.spec.tsx @@ -28,11 +28,9 @@ const styles: StyleRulesCallback<'root'> = ({ palette, spacing }) => ({ }, }); -const StyledViewOne: React.SFC> = ({ classes, text }) => ( +const StyledExampleOne = withStyles(styles)(({ classes, text }) => (

{text}
-); -const StyledExampleOne = withStyles(styles)(StyledViewOne); - +)); ; // Example 2 @@ -138,7 +136,7 @@ function OverridesTheme() { ); } -// withTheme. +// withTheme const ComponentWithTheme = withTheme()(({ theme }) =>
{theme.spacing.unit}
); ; diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index 6feb7dc8ac9b54..c29eaf78ff98ff 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -16,12 +16,11 @@ interface Props { variant: TypographyProps['variant']; } -const Decoratee: React.SFC> = ({ text, variant, color, classes }) => ( +const DecoratedSFC = decorate(({ text, variant, color, classes }) => ( {text} -); -const DecoratedSFC = decorate(Decoratee); +)); const DecoratedClass = decorate( class extends React.Component> { From c01d787ec4f07b35d882ea28cbfe7a09a57fcd85 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 10:17:12 -0400 Subject: [PATCH 07/16] Commit for review and suggestions; DOES NOT COMPILE. Remove commented-on code from withStyles; use ConsistentWith typewrapper; add SFC test for withWidth add FAILING test for doc scenario 2 (withStyles of union) --- .../src/pages/guides/typescript/typescript.md | 37 --------- src/index.d.ts | 3 + src/styles/withStyles.d.ts | 21 +---- src/styles/withTheme.d.ts | 6 +- src/utils/withWidth.d.ts | 6 +- src/utils/withWidth.spec.tsx | 7 ++ test/typescript/styling-comparison.spec.tsx | 80 +++++++++++++++++++ 7 files changed, 98 insertions(+), 62 deletions(-) diff --git a/docs/src/pages/guides/typescript/typescript.md b/docs/src/pages/guides/typescript/typescript.md index 46708a793c74c9..6fef604ea64618 100644 --- a/docs/src/pages/guides/typescript/typescript.md +++ b/docs/src/pages/guides/typescript/typescript.md @@ -56,43 +56,6 @@ const DecoratedClass = decorate( ); ``` -Note that in the class example you didn't need to annotate `` in the call to `decorate`; type inference took care of everything. However, there are 2 scenarios where you _do_ need to provide an explicit type argument to `decorate`. - -Scenario 1: your styled component takes _no_ additional props in addition to `classes`. The natural thing would be to write: - -```jsx -import { WithStyles } from 'material-ui/styles'; - -const DecoratedNoProps = decorate( - class extends React.Component> { - render() { - return ( - - Hello, World! - - ); - } - } -); -``` - -Unfortunately, TypeScript infers the wrong type in this case and you'll have trouble when you go to make an element of this component. In this case, you'll need to provide an explicit `{}` type argument, like so: - -```jsx -import { WithStyles } from 'material-ui/styles'; - -const DecoratedNoProps = decorate<{}>( // <-- note the type argument! - class extends React.Component> { - render() { - return ( - - Hello, World! - - ); - } - } -); -``` Scenario 2: `Props` is a union type. Again, to avoid getting a compiler error, you'll need to provide an explict type argument: diff --git a/src/index.d.ts b/src/index.d.ts index 0fdc4988230111..d7c950dd525be2 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -46,6 +46,9 @@ type Diff = ({ [P in T]: P } & /** @internal */ export type Omit = Pick>; +/** @internal */ +export type ConsistentWith = Partial & Record; + export namespace PropTypes { type Alignment = 'inherit' | 'left' | 'center' | 'right' | 'justify'; type Color = 'inherit' | 'primary' | 'secondary' | 'default'; diff --git a/src/styles/withStyles.d.ts b/src/styles/withStyles.d.ts index b57fff46e7fa68..d0b6c0d8c85a07 100644 --- a/src/styles/withStyles.d.ts +++ b/src/styles/withStyles.d.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { WithTheme } from '../styles/withTheme'; -import { Omit } from '..'; +import { Omit, ConsistentWith } from '..'; import { Theme } from './createMuiTheme'; /** @@ -39,26 +39,9 @@ export interface StyledComponentProps { innerRef?: React.Ref; } -/* - The Omit below (rather than the canonical Omit>) - is used to allow interoperability with withTheme, which may provide the 'theme' prop. - */ -/* -export default function withStyles( - style: StyleRules | StyleRulesCallback, - options?: WithStylesOptions, -):

>( - component: React.ComponentType

, -) => React.ComponentType & StyledComponentProps>; -*/ - -interface MaybeWithStyles extends Partial> { - [k: string]: any; -} - export default function withStyles( style: StyleRules | StyleRulesCallback, options?: WithStylesOptions, -):

>( +):

>>( component: React.ComponentType

>, ) => React.ComponentType & StyledComponentProps>; diff --git a/src/styles/withTheme.d.ts b/src/styles/withTheme.d.ts index a0f14f0e075c6f..c99740626b47d2 100644 --- a/src/styles/withTheme.d.ts +++ b/src/styles/withTheme.d.ts @@ -1,12 +1,12 @@ import { Theme } from './createMuiTheme'; -import { Omit } from '..'; +import { Omit, ConsistentWith } from '..'; export interface WithTheme { theme: Theme; } -declare const withTheme: () =>

( - component: React.ComponentType

, +declare const withTheme: () =>

>( + component: React.ComponentType

, ) => React.ComponentClass>; export default withTheme; diff --git a/src/utils/withWidth.d.ts b/src/utils/withWidth.d.ts index 61f701cf6a4a8f..00fba1c5f6dec1 100644 --- a/src/utils/withWidth.d.ts +++ b/src/utils/withWidth.d.ts @@ -1,5 +1,5 @@ import { Breakpoint } from '../styles/createBreakpoints'; -import { Omit } from '..'; +import { Omit, ConsistentWith } from '..'; export interface WithWidthOptions { resizeInterval: number; @@ -23,6 +23,6 @@ export function isWidthUp( export default function withWidth( options?: WithWidthOptions, -):

( - component: React.ComponentType

, +):

>( + component: React.ComponentType

, ) => React.ComponentClass & Partial>; diff --git a/src/utils/withWidth.spec.tsx b/src/utils/withWidth.spec.tsx index db4d42b3b916ef..2ada0716a95c7c 100644 --- a/src/utils/withWidth.spec.tsx +++ b/src/utils/withWidth.spec.tsx @@ -34,3 +34,10 @@ export class Hello extends React.Component; + +const WidthSFC = withWidth()<{ + // shouldn't need to specify width here; it's a given + name: string; +}>(({ width, name }) =>

hello, {name}
); + +; diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index c29eaf78ff98ff..20851e6cfc3343 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { Omit } from '../../src'; import Typography, { TypographyProps } from '../../src/Typography/Typography'; import { withStyles, WithStyles } from '../../src/styles'; @@ -46,3 +47,82 @@ const DecoratedNoProps = decorate( const sfcElem = ; const classElem = ; const noPropsElem = ; + +// This is the "scenario 2" example straight from the doc, then invoked: + +interface Book { + category: 'book'; + author: string; +} + +interface Painting { + category: 'painting'; + artist: string; +} + +type ArtProps = Book | Painting; + +const DecoratedUnionProps = decorate( // <-- without the type argument, we'd get a compiler error! + class extends React.Component> { + render() { + const props = this.props; + return ( + + {props.category === 'book' ? props.author : props.artist} + + ); + } + }, +); + +//FAILS!!! +const unionPropElem = ; + +////// +// Here's the same thing, using withStyles directly, for clarity: + +class UnstyledUnion extends React.Component> { + render() { + const props = this.props; + return ( + + {props.category === 'book' ? props.author : props.artist} + + ); + } +} + +const StyledUnion = withStyles({ root: { left: 0 } })(UnstyledUnion); +const styledUnion = ; + +// This gives a clear error message, showing what's going on underneath + +type StyleRemoved = Omit, keyof WithStyles<'root'>>; +const styleRemoved: StyleRemoved = { category: 'book', author: 'x' }; + +// Remove the union, and all works: + +type Confederacy = Omit, keyof WithStyles<'root'>>; + +// Plan B: require wrapping of unions + +interface WrappedArtProps { + artProp: Book | Painting; +} + +const DecoratedWrappedArtProps = decorate( // <-- without the type argument, we'd get a compiler error! + class extends React.Component> { + render() { + const props = this.props; + return ( + + {props.artProp.category === 'book' ? props.artProp.author : props.artProp.artist} + + ); + } + }, +); + +const decoratedWrappedArtProps = ( + +); From 89d2a6075f3f54fd76b7a5ae814c384577f41c01 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 14:49:11 -0400 Subject: [PATCH 08/16] Redefine with* to remove Omit. Trim obsolete doc. Rename "decorate" function to "withMyStyles" in doc. Remove redundant failing tests added to clarify Omit problem. --- .../src/pages/guides/typescript/typescript.md | 40 ++------------- src/styles/withStyles.d.ts | 11 +++-- src/styles/withTheme.d.ts | 2 +- src/utils/withWidth.d.ts | 2 +- test/typescript/styling-comparison.spec.tsx | 49 ------------------- 5 files changed, 13 insertions(+), 91 deletions(-) diff --git a/docs/src/pages/guides/typescript/typescript.md b/docs/src/pages/guides/typescript/typescript.md index 6fef604ea64618..8c168e2f4b3d73 100644 --- a/docs/src/pages/guides/typescript/typescript.md +++ b/docs/src/pages/guides/typescript/typescript.md @@ -8,7 +8,7 @@ Have a look at the [Create React App with TypeScript](https://github.com/mui-org The usage of `withStyles` in TypeScript can be a little tricky, so it's worth showing some examples. You can first call `withStyles()` to create a decorator function, like so: ```js -const decorate = withStyles(({ palette, spacing }) => ({ +const withMyStyles = withStyles(({ palette, spacing }) => ({ root: { padding: spacing.unit, backgroundColor: palette.background.default, @@ -30,7 +30,7 @@ interface Props { Functional components are straightforward: ```jsx -const DecoratedSFC = decorate(({ text, type, color, classes }) => ( +const DecoratedSFC = withMyStyles(({ text, type, color, classes }) => ( {text} @@ -42,7 +42,7 @@ Class components are a little more cumbersome. Due to a [current limitation in T ```jsx import { WithStyles } from 'material-ui/styles'; -const DecoratedClass = decorate( +const DecoratedClass = withMyStyles( class extends React.Component> { render() { const { text, type, color, classes } = this.props @@ -56,40 +56,6 @@ const DecoratedClass = decorate( ); ``` - -Scenario 2: `Props` is a union type. Again, to avoid getting a compiler error, you'll need to provide an explict type argument: - -```jsx -import { WithStyles } from 'material-ui/styles'; - -interface Book { - category: "book"; - author: string; -} - -interface Painting { - category: "painting"; - artist: string; -} - -type Props = Book | Painting; - -const DecoratedUnionProps = decorate( // <-- without the type argument, we'd get a compiler error! - class extends React.Component> { - render() { - const props = this.props; - return ( - - {props.category === "book" ? props.author : props.artist} - - ); - } - } -); -``` - -To avoid worrying about these 2 edge cases, it may be a good habit to always provide an explicit type argument to `decorate`. - ### Injecting Multiple Classes Injecting multiple classes into a component is as straightforward as possible. Take the following code for example. The classes `one` and `two` are both available with type information on the `classes`-prop passed in by `withStyles`. diff --git a/src/styles/withStyles.d.ts b/src/styles/withStyles.d.ts index d0b6c0d8c85a07..ba5ec2c13f5710 100644 --- a/src/styles/withStyles.d.ts +++ b/src/styles/withStyles.d.ts @@ -42,6 +42,11 @@ export interface StyledComponentProps { export default function withStyles( style: StyleRules | StyleRulesCallback, options?: WithStylesOptions, -):

>>( - component: React.ComponentType

>, -) => React.ComponentType & StyledComponentProps>; +): { + ( + component: React.ComponentType>, + ): React.ComponentType>; +

>>( + component: React.ComponentType

>, + ): React.ComponentType

>; +} diff --git a/src/styles/withTheme.d.ts b/src/styles/withTheme.d.ts index c99740626b47d2..6860f60ba1943e 100644 --- a/src/styles/withTheme.d.ts +++ b/src/styles/withTheme.d.ts @@ -7,6 +7,6 @@ export interface WithTheme { declare const withTheme: () =>

>( component: React.ComponentType

, -) => React.ComponentClass>; +) => React.ComponentClass

; export default withTheme; diff --git a/src/utils/withWidth.d.ts b/src/utils/withWidth.d.ts index 00fba1c5f6dec1..fafb5a05c1ba49 100644 --- a/src/utils/withWidth.d.ts +++ b/src/utils/withWidth.d.ts @@ -25,4 +25,4 @@ export default function withWidth( options?: WithWidthOptions, ):

>( component: React.ComponentType

, -) => React.ComponentClass & Partial>; +) => React.ComponentClass

>; diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index 20851e6cfc3343..2404f70b0fb1f6 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -75,54 +75,5 @@ const DecoratedUnionProps = decorate( // <-- without the type argument }, ); -//FAILS!!! const unionPropElem = ; -////// -// Here's the same thing, using withStyles directly, for clarity: - -class UnstyledUnion extends React.Component> { - render() { - const props = this.props; - return ( - - {props.category === 'book' ? props.author : props.artist} - - ); - } -} - -const StyledUnion = withStyles({ root: { left: 0 } })(UnstyledUnion); -const styledUnion = ; - -// This gives a clear error message, showing what's going on underneath - -type StyleRemoved = Omit, keyof WithStyles<'root'>>; -const styleRemoved: StyleRemoved = { category: 'book', author: 'x' }; - -// Remove the union, and all works: - -type Confederacy = Omit, keyof WithStyles<'root'>>; - -// Plan B: require wrapping of unions - -interface WrappedArtProps { - artProp: Book | Painting; -} - -const DecoratedWrappedArtProps = decorate( // <-- without the type argument, we'd get a compiler error! - class extends React.Component> { - render() { - const props = this.props; - return ( - - {props.artProp.category === 'book' ? props.artProp.author : props.artProp.artist} - - ); - } - }, -); - -const decoratedWrappedArtProps = ( - -); From bcbf70192772fa22fbc8e83e6a04749beff7c275 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 19:06:35 -0400 Subject: [PATCH 09/16] Bring back explicit generic parameter on union case in test, doc. In package.json, up-rev Typescript to >2.6.2, and print Typescript version in "typescript" run command. --- .../src/pages/guides/typescript/typescript.md | 33 +++++++++++++++++++ package.json | 4 +-- test/typescript/styling-comparison.spec.tsx | 4 +-- yarn.lock | 6 ++-- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/docs/src/pages/guides/typescript/typescript.md b/docs/src/pages/guides/typescript/typescript.md index 8c168e2f4b3d73..3289a1599cde0b 100644 --- a/docs/src/pages/guides/typescript/typescript.md +++ b/docs/src/pages/guides/typescript/typescript.md @@ -56,6 +56,39 @@ const DecoratedClass = withMyStyles( ); ``` +When your `props` are a union, Typescript needs you to explicitly tell it the type, +by providing an explicit generic `` parameter to `withMyStyles`: + +```jsx +import { WithStyles } from 'material-ui/styles'; + +interface Book { + category: "book"; + author: string; +} + +interface Painting { + category: "painting"; + artist: string; +} + +type Props = Book | Painting; + +const DecoratedUnionProps = withMyStyles( // <-- without the type argument, we'd get a compiler error! + class extends React.Component> { + render() { + const props = this.props; + return ( + + {props.category === "book" ? props.author : props.artist} + + ); + } + } +); +``` + + ### Injecting Multiple Classes Injecting multiple classes into a component is as straightforward as possible. Take the following code for example. The classes `one` and `two` are both available with type information on the `classes`-prop passed in by `withStyles`. diff --git a/package.json b/package.json index 9e0c316366c635..88203f8c5b865d 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test:coverage:html": "cross-env NODE_ENV=test BABEL_ENV=coverage nyc mocha test/**/*.test.js src/{,**/}*.test.js && nyc report --reporter=html", "test:karma": "cross-env NODE_ENV=test karma start test/karma.conf.js --single-run", "test:regressions": "webpack --config test/regressions/webpack.config.js && rimraf test/regressions/screenshots/chrome/* && vrtest run --config test/vrtest.config.js --record", - "typescript": "tsc -p tsconfig.json", + "typescript": "tsc -v && tsc -p tsconfig.json", "flow": "flow --show-all-errors", "argos": "argos upload test/regressions/screenshots/chrome --token $ARGOS_TOKEN || true", "prebuild": "rimraf build", @@ -187,7 +187,7 @@ "rimraf": "^2.6.2", "sinon": "^4.1.2", "size-limit": "0.14.1", - "typescript": "^2.6.1", + "typescript": "2.6.2", "url-loader": "^1.0.1", "vrtest": "^0.2.0", "webfontloader": "^1.6.28", diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index 2404f70b0fb1f6..779873a9d2c0ba 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -68,11 +68,11 @@ const DecoratedUnionProps = decorate( // <-- without the type argument const props = this.props; return ( - {props.category === 'book' ? props.author : props.artist} + {props.category === "book" ? props.author : props.artist} ); } - }, + } ); const unionPropElem = ; diff --git a/yarn.lock b/yarn.lock index 7d280792292ab4..c8b93788205556 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10173,9 +10173,9 @@ typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^2.6.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624" +typescript@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" ua-parser-js@^0.7.9: version "0.7.17" From 9fbbbc1e96847e03fdd0c67bd1dcaf3039671900 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 19:58:23 -0400 Subject: [PATCH 10/16] Rename withMyStyles back to decorate. Use single quotes in test. In package.json, revert typescript to 2.6.1, remove version-printing run command In package.lock, revert typescript to 2.6.1. --- docs/src/pages/guides/typescript/typescript.md | 10 +++++----- package.json | 4 ++-- test/typescript/styling-comparison.spec.tsx | 2 +- yarn.lock | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/src/pages/guides/typescript/typescript.md b/docs/src/pages/guides/typescript/typescript.md index 3289a1599cde0b..59efb90b4bc936 100644 --- a/docs/src/pages/guides/typescript/typescript.md +++ b/docs/src/pages/guides/typescript/typescript.md @@ -8,7 +8,7 @@ Have a look at the [Create React App with TypeScript](https://github.com/mui-org The usage of `withStyles` in TypeScript can be a little tricky, so it's worth showing some examples. You can first call `withStyles()` to create a decorator function, like so: ```js -const withMyStyles = withStyles(({ palette, spacing }) => ({ +const decorate = withStyles(({ palette, spacing }) => ({ root: { padding: spacing.unit, backgroundColor: palette.background.default, @@ -30,7 +30,7 @@ interface Props { Functional components are straightforward: ```jsx -const DecoratedSFC = withMyStyles(({ text, type, color, classes }) => ( +const DecoratedSFC = decorate(({ text, type, color, classes }) => ( {text} @@ -42,7 +42,7 @@ Class components are a little more cumbersome. Due to a [current limitation in T ```jsx import { WithStyles } from 'material-ui/styles'; -const DecoratedClass = withMyStyles( +const DecoratedClass = decorate( class extends React.Component> { render() { const { text, type, color, classes } = this.props @@ -57,7 +57,7 @@ const DecoratedClass = withMyStyles( ``` When your `props` are a union, Typescript needs you to explicitly tell it the type, -by providing an explicit generic `` parameter to `withMyStyles`: +by providing an explicit generic `` parameter to `decorate`: ```jsx import { WithStyles } from 'material-ui/styles'; @@ -74,7 +74,7 @@ interface Painting { type Props = Book | Painting; -const DecoratedUnionProps = withMyStyles( // <-- without the type argument, we'd get a compiler error! +const DecoratedUnionProps = decorate( // <-- without the type argument, we'd get a compiler error! class extends React.Component> { render() { const props = this.props; diff --git a/package.json b/package.json index 88203f8c5b865d..48ecc4f3b37e11 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test:coverage:html": "cross-env NODE_ENV=test BABEL_ENV=coverage nyc mocha test/**/*.test.js src/{,**/}*.test.js && nyc report --reporter=html", "test:karma": "cross-env NODE_ENV=test karma start test/karma.conf.js --single-run", "test:regressions": "webpack --config test/regressions/webpack.config.js && rimraf test/regressions/screenshots/chrome/* && vrtest run --config test/vrtest.config.js --record", - "typescript": "tsc -v && tsc -p tsconfig.json", + "typescript": "tsc -p tsconfig.json", "flow": "flow --show-all-errors", "argos": "argos upload test/regressions/screenshots/chrome --token $ARGOS_TOKEN || true", "prebuild": "rimraf build", @@ -187,7 +187,7 @@ "rimraf": "^2.6.2", "sinon": "^4.1.2", "size-limit": "0.14.1", - "typescript": "2.6.2", + "typescript": "2.6.1", "url-loader": "^1.0.1", "vrtest": "^0.2.0", "webfontloader": "^1.6.28", diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index 779873a9d2c0ba..de2c2f0a6bfa64 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -68,7 +68,7 @@ const DecoratedUnionProps = decorate( // <-- without the type argument const props = this.props; return ( - {props.category === "book" ? props.author : props.artist} + {props.category === 'book' ? props.author : props.artist} ); } diff --git a/yarn.lock b/yarn.lock index c8b93788205556..cba3a40c5136b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10173,9 +10173,9 @@ typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" +typescript@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631" ua-parser-js@^0.7.9: version "0.7.17" From e66b8ea90a0b742fe3c90a55afbcf4cdc7c7bb8f Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 20:02:57 -0400 Subject: [PATCH 11/16] Add trailing comma to function params in styling-comparison test. --- test/typescript/styling-comparison.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index de2c2f0a6bfa64..2404f70b0fb1f6 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -72,7 +72,7 @@ const DecoratedUnionProps = decorate( // <-- without the type argument ); } - } + }, ); const unionPropElem = ; From 0b47fb304a64eb8feab73738de1cd321ff92c642 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 20:10:10 -0400 Subject: [PATCH 12/16] Fix package.json, yarn.lock. --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 48ecc4f3b37e11..9e0c316366c635 100644 --- a/package.json +++ b/package.json @@ -187,7 +187,7 @@ "rimraf": "^2.6.2", "sinon": "^4.1.2", "size-limit": "0.14.1", - "typescript": "2.6.1", + "typescript": "^2.6.1", "url-loader": "^1.0.1", "vrtest": "^0.2.0", "webfontloader": "^1.6.28", diff --git a/yarn.lock b/yarn.lock index cba3a40c5136b1..7d280792292ab4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10173,9 +10173,9 @@ typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631" +typescript@^2.6.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624" ua-parser-js@^0.7.9: version "0.7.17" From 748af3997b4084878e13e18a9daea12e3a7e64c8 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 20:14:03 -0400 Subject: [PATCH 13/16] Remove newline from typescript doc. --- docs/src/pages/guides/typescript/typescript.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/pages/guides/typescript/typescript.md b/docs/src/pages/guides/typescript/typescript.md index 59efb90b4bc936..842527df0f8fef 100644 --- a/docs/src/pages/guides/typescript/typescript.md +++ b/docs/src/pages/guides/typescript/typescript.md @@ -56,8 +56,7 @@ const DecoratedClass = decorate( ); ``` -When your `props` are a union, Typescript needs you to explicitly tell it the type, -by providing an explicit generic `` parameter to `decorate`: +When your `props` are a union, Typescript needs you to explicitly tell it the type, by providing an explicit generic `` parameter to `decorate`: ```jsx import { WithStyles } from 'material-ui/styles'; From b24250bea9b48f3745018470883ba9fcdb7944e5 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 20:23:17 -0400 Subject: [PATCH 14/16] Remove needless Omit imports. --- src/styles/withStyles.d.ts | 2 +- src/styles/withTheme.d.ts | 2 +- src/utils/withWidth.d.ts | 2 +- test/typescript/styling-comparison.spec.tsx | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/styles/withStyles.d.ts b/src/styles/withStyles.d.ts index ba5ec2c13f5710..9d8cd8ecdd39d6 100644 --- a/src/styles/withStyles.d.ts +++ b/src/styles/withStyles.d.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { WithTheme } from '../styles/withTheme'; -import { Omit, ConsistentWith } from '..'; +import { ConsistentWith } from '..'; import { Theme } from './createMuiTheme'; /** diff --git a/src/styles/withTheme.d.ts b/src/styles/withTheme.d.ts index 6860f60ba1943e..5a455a06dffe92 100644 --- a/src/styles/withTheme.d.ts +++ b/src/styles/withTheme.d.ts @@ -1,5 +1,5 @@ import { Theme } from './createMuiTheme'; -import { Omit, ConsistentWith } from '..'; +import { ConsistentWith } from '..'; export interface WithTheme { theme: Theme; diff --git a/src/utils/withWidth.d.ts b/src/utils/withWidth.d.ts index fafb5a05c1ba49..575bb67566c9c9 100644 --- a/src/utils/withWidth.d.ts +++ b/src/utils/withWidth.d.ts @@ -1,5 +1,5 @@ import { Breakpoint } from '../styles/createBreakpoints'; -import { Omit, ConsistentWith } from '..'; +import { ConsistentWith } from '..'; export interface WithWidthOptions { resizeInterval: number; diff --git a/test/typescript/styling-comparison.spec.tsx b/test/typescript/styling-comparison.spec.tsx index 2404f70b0fb1f6..78e3ee5922d245 100644 --- a/test/typescript/styling-comparison.spec.tsx +++ b/test/typescript/styling-comparison.spec.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { Omit } from '../../src'; import Typography, { TypographyProps } from '../../src/Typography/Typography'; import { withStyles, WithStyles } from '../../src/styles'; From ac934c192bf4f2320fd2dc119d63ad0b6191ba78 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 20:48:31 -0400 Subject: [PATCH 15/16] Remove extra explicit from doc, add trailing semicolon to function --- docs/src/pages/guides/typescript/typescript.md | 2 +- src/styles/withStyles.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/pages/guides/typescript/typescript.md b/docs/src/pages/guides/typescript/typescript.md index 842527df0f8fef..c1f3af2382248c 100644 --- a/docs/src/pages/guides/typescript/typescript.md +++ b/docs/src/pages/guides/typescript/typescript.md @@ -56,7 +56,7 @@ const DecoratedClass = decorate( ); ``` -When your `props` are a union, Typescript needs you to explicitly tell it the type, by providing an explicit generic `` parameter to `decorate`: +When your `props` are a union, Typescript needs you to explicitly tell it the type, by providing a generic `` parameter to `decorate`: ```jsx import { WithStyles } from 'material-ui/styles'; diff --git a/src/styles/withStyles.d.ts b/src/styles/withStyles.d.ts index 9d8cd8ecdd39d6..37abaf330a6a24 100644 --- a/src/styles/withStyles.d.ts +++ b/src/styles/withStyles.d.ts @@ -49,4 +49,4 @@ export default function withStyles(

>>( component: React.ComponentType

>, ): React.ComponentType

>; -} +}; From ea20df9c5f51453110a10c8815f22a3391c677d0 Mon Sep 17 00:00:00 2001 From: estaub Date: Mon, 16 Apr 2018 21:09:19 -0400 Subject: [PATCH 16/16] Remove comment from styling-comparison.spec.tsx --- .../material-ui/test/typescript/styling-comparison.spec.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/material-ui/test/typescript/styling-comparison.spec.tsx b/packages/material-ui/test/typescript/styling-comparison.spec.tsx index 78e3ee5922d245..c885436463bdd3 100644 --- a/packages/material-ui/test/typescript/styling-comparison.spec.tsx +++ b/packages/material-ui/test/typescript/styling-comparison.spec.tsx @@ -47,8 +47,6 @@ const sfcElem = ; const noPropsElem = ; -// This is the "scenario 2" example straight from the doc, then invoked: - interface Book { category: 'book'; author: string;