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

Derivative Shorthand Styled Components Lose Override Typing #14586

Closed
2 tasks done
johnrichter opened this issue Feb 19, 2019 · 2 comments · Fixed by #15381
Closed
2 tasks done

Derivative Shorthand Styled Components Lose Override Typing #14586

johnrichter opened this issue Feb 19, 2019 · 2 comments · Fixed by #15381
Milestone

Comments

@johnrichter
Copy link
Contributor

johnrichter commented Feb 19, 2019

I discovered MUI last week and am floored by its quality, polish, and ease of use. You all rock. ❤️

I've been experimenting with using MUI as the main UI library behind a product I'm building and have I've been using the withStyles() shorthand capability with great success until today when I needed to style an Avatar and then use the component override capability via typing of the return type of withStyles() and styled() (i.e. ComponentCreator()). Is this intended? Am I doing something wrong? I'm going to assume PEBKAC, but if I can reveal a bug or missing feature I'm all for making this lib better 😄

Shorthand Docs

  • This is not a v0.x issue.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior 🤔

After I create a component from withStyles({})(Component) or styled(Component)({}) I should be able to use the overrides as I would with Component.

Current Behavior 😯

Both functions return this error: Property 'component' does not exist on type 'IntrinsicAttributes & Pick<DefaultComponentProps<{...

Steps to Reproduce 🕹

Just put the following code in a react app with Typescript and try to compile.

import * as React from 'react';
import { Avatar, withStyles } from '@material-ui/core';
import { styled } from '@material-ui/styles';

export const MyAvatarWithStyles = withStyles({
    root: { borderRadius: 0, height: '100%', width: 'auto', backgroundColor: 'red' },
    img: { width: 'auto' }
})(Avatar);

export const MyAvatarStyled = styled(Avatar)({
    borderRadius: 0, height: '100%', width: 'auto', backgroundColor: 'red'
});

export function StyledAvatars(): React.ReactElement<any> {
    return (
        <React.Fragment>
            <MyAvatarWithStyles component="a">AB</MyAvatarWithStyles>
            <MyAvatarStyled>AB</MyAvatarStyled>
            <MyAvatarStyled component="a">AB</MyAvatarStyled>
        </React.Fragment>
    );
}

I've created a sandbox for my issue, but CodeSandbox's use of tsc doesn't work? Coincidentally, this also produces some unexpected styling on second <MyAvatarStyled component='a' /> call

I've tried creating return types using the return types of withStyles() and styled() and typing each derivative component, but had no luck. Aside: this kind of exported type would be very helpful for me as I require typedefs for all of my variables, parameters, function return types, etc.

For example, I have these types in file:

import { Omit, StyledComponentProps, Theme as MUITheme, WithStyles, ConsistentWith } from '@material-ui/core';
import { StylesProviderProps } from '@material-ui/styles/StylesProvider';
import { WithStylesOptions } from '@material-ui/core/styles/withStyles';

// The return type from MUI's `styled(C)(styles)` function call
export type StyledComponent<C extends React.ReactType> = React.ComponentType<
    Omit<JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>, 'classes' | 'className'> &
    StyledComponentProps<'root'> & { className?: string }
>;

// The props for all `styled(C)((props: StyledProps) => CSSProperties)` styling function
export type StyledProps<Theme extends MUITheme = MUITheme> = { stylesOptions: StylesProviderProps, theme: Theme };

// The return type from MUI's `withTheme(styles)(C)` function call
export type WithStylesComponent<
    C extends React.ComponentType<
        ConsistentWith<React.ComponentProps<C>, WithStyles<ClassKey, Options['withTheme']>>
    >,
    ClassKey extends string,
    Options extends WithStylesOptions<ClassKey> = {}
    > = React.ComponentType<
        Omit<
            JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>,
            keyof WithStyles<ClassKey, Options['withTheme']>
        > &
        StyledComponentProps<ClassKey>
    >;

I use them like this:

const MyCard: StyledComponent<typeof Card> = styled(Card)({
    width: '300px'
});

Context 🔦

I'm trying to customize the Avatar component to act as a logo for various size images all normalized by a specific height, 20px for example. Depending on some props in the wrapper component, I'd like to turn these logos into links to other places in my app.

Your Environment 🌎

My app is based off of CRA v2 with their built in Typescript support via tsc and Babel

Tech Version
Material-UI (core / lab / styles) v4.0.0-alpha.0
React & DOM v16.8.2
React-Scripts v2.1.5
Browser Chrome 72.0.3626.109 (Official Build) (64-bit)
TypeScript v3.3.3
VSCode v1.31.1

tsconfig.json

  • I admittedly created this early last year or maybe before that. I'm not sure if these settings are reasonable anymore. For fun I tried copying the tsconfig from the base MUI Typescript Sandbox and got the same error result.
{
    "compilerOptions": {
        "allowJs": true,
        "allowSyntheticDefaultImports": true,
        "allowUnreachableCode": false,
        "allowUnusedLabels": false,
        "alwaysStrict": true,
        "charset": "utf8",
        "checkJs": true,
        "declaration": false,
        "diagnostics": true,
        "downlevelIteration": true,
        "emitDecoratorMetadata": true,
        "esModuleInterop": true,
        "experimentalDecorators": true,
        "forceConsistentCasingInFileNames": true,
        "importHelpers": true,
        "inlineSourceMap": false,
        "inlineSources": true,
        "isolatedModules": true,
        "jsx": "preserve",
        "lib": [ "dom", "dom.iterable", "esnext" ],
        "listEmittedFiles": true,
        "listFiles": true,
        "locale": "en-us",
        "module": "esnext",
        "moduleResolution": "node",
        "newLine": "LF",
        "noEmit": true,
        "noFallthroughCasesInSwitch": false,
        "noImplicitAny": true,
        "noImplicitReturns": false,
        "noImplicitThis": true,
        "noImplicitUseStrict": false,
        "noResolve": false,
        "noUnusedLocals": false,
        "noUnusedParameters": false,
        "outDir": "build/dist",
        "preserveConstEnums": true,
        "pretty": true,
        "removeComments": true,
        "resolveJsonModule": true,
        "rootDir": "src",
        "skipLibCheck": true,
        "sourceMap": true,
        "strict": true,
        "strictNullChecks": true,
        "stripInternal": true,
        "suppressExcessPropertyErrors": false,
        "suppressImplicitAnyIndexErrors": false,
        "target": "es5",
        "traceResolution": false
    },
    "include": [
        "src"
    ]
}
@eps1lon eps1lon added the package: styles Specific to @mui/styles. Legacy package, @material-ui/styled-engine is taking over in v5. label Feb 19, 2019
@oliviertassinari oliviertassinari added typescript and removed package: styles Specific to @mui/styles. Legacy package, @material-ui/styled-engine is taking over in v5. labels Feb 20, 2019
@eps1lon
Copy link
Member

eps1lon commented Mar 12, 2019

Coincidentally, this also produces some unexpected styling on second call

styled changes how the component property works. I wouldn't recommend you use it for actual components since it's highly confusing. Only use it if you wrap intrinsic components like 'button' or 'table' i.e. <styled('div') component="span" /> is fine but <styled(Avatar) component="span" /> is not.

The issue with the lost component prop with withStyles is caused by how we type our components. I guess overloading the call signature breaks interop with any hoc. /cc @pelotom

@eps1lon
Copy link
Member

eps1lon commented Apr 17, 2019

This is a design limitation of TypeScript. Workaround:

export const MyAvatarWithStyles = withStyles({
    root: { borderRadius: 0, height: '100%', width: 'auto', backgroundColor: 'red' },
    img: { width: 'auto' }
-})(Avatar);
+})(Avatar) as typeof Avatar;

See #15381 for more details.

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

Successfully merging a pull request may close this issue.

3 participants