Material-ui: The type of styled-component does not match for "strict: true"

Created on 17 Dec 2018  Â·  11Comments  Â·  Source: mui-org/material-ui

When we use styled-component in TypeScript to inherit the Material-UI component, props type does not match.
How do I overwrite a style with styled-component?

There is a sample on how to inherit in the case of styled-component in the following link, but this sample will generate an error if it is set to strict: true in tsconfig setting.

https://material-ui.com/guides/interoperability/#styled-components

import React from 'react';
import styled from 'styled-components';
import Button from '@material-ui/core/Button';

const StyledButton = styled(Button)`
  background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%);
  border-radius: 3px;
  border: 0;
  color: white;
  height: 48px;
  padding: 0 30px;
  box-shadow: 0 3px 5px 2px rgba(255, 105, 135, .3);
`;

function StyledComponentsButton() {
  return (
    <div>
      <Button>
        Material-UI
      </Button>
      <StyledButton>
        Styled Components
      </StyledButton>
    </div>
  );
}

export default StyledComponentsButton;

The way I tried is to cast the component with React.SFC and then pass the props type of the material-ui component to that generics.

import TextField, { TextFieldProps } from "@material-ui/core/TextField";

export const StyledTextField = styled(TextField as React.SFC<TextFieldProps>)`
  // some style
`;

in this case

Type '{ disabled: true; label: string; defaultValue: string; variant: "outlined"; }' is missing the following properties from type 'Pick<Pick<TextFieldProps.....

The above error will be displayed :(

It was able to solve by the following method.

interface TextFieldProps {
  disabled: boolean;
  label: string;
  defaultValue: string;
  variant: string;
}

export const StyledTextField = styled(TextField as React.SFC<TextFieldProps>)`
  // some style
`;

However, I do not think this is a nice implementation. Required Props
It is because you have to re-implement the interface every time it changes.


@material-ui/core version: 3.6.2
@material-ui/icons version: 3.0.1
styled-components version: 4.1.2
typescript version: 3.2.2

macOS: 10.13.6

typescript

Most helpful comment

I encourage you to use

const StyledButton = styled(Button)`` as typeof Button;

This will remove any type information for props added by styled-components. However, most of these shouldn't be necessary anymore. Please let me know if there are any props from styled-components that are essential in your opinion.

There are both issues with the styled-component types (union props are lost: changing this caused a 20x perf regression in ts 3.4) and TypeScript design limitations (argument inference only considers one function overload).

All 11 comments

There's some documentation on the TypeScript Guide page related to using type widening with createStyles to overcome some of these issues: https://material-ui.com/guides/typescript/

Same issue here.

In case it's helpful file with issue is:

import { TextField } from '@material-ui/core';
import styled from '../../theme';

export const CustomSelect = styled(TextField)`
  width: 100%;
  max-width: 300px;
`;

And tsconfig is:

{
  "compilerOptions": {
    "allowJs": true,
    "baseUrl": ".",
    "forceConsistentCasingInFileNames": true,
    "jsx": "react",
    "lib": ["es6", "dom"],
    "module": "esnext",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "outDir": "build",
    "paths": {
      "src/*": ["src/*"],
      "test/*": ["test/*"]
    },
    "rootDir": "src",
    "sourceMap": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "target": "es5",
    "typeRoots": ["src/typings", "node_modules/@types"],
  },
  "include": [
    "src",
    "test"
  ]
}

These problems can also be overridden by style unless it is strict: true. If you check strictly against type, you will get an error.

This happens because interface is defined as follows, but you can also see the idea of the author.

export interface BaseTextFieldProps
  extends StandardProps<FormControlProps, TextFieldClassKey, 'onChange' | 'defaultValue'> {
  autoComplete?: string;
  autoFocus?: boolean;
  children?: React.ReactNode;
  defaultValue?: string | number;
  disabled?: boolean;
  error?: boolean;
  FormHelperTextProps?: Partial<FormHelperTextProps>;
  fullWidth?: boolean;
  helperText?: React.ReactNode;
  id?: string;
  InputLabelProps?: Partial<InputLabelProps>;
  inputRef?: React.Ref<any> | React.RefObject<any>;
  label?: React.ReactNode;
  margin?: PropTypes.Margin;
  multiline?: boolean;
  name?: string;
  onChange?: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>;
  placeholder?: string;
  required?: boolean;
  rows?: string | number;
  rowsMax?: string | number;
  select?: boolean;
  SelectProps?: Partial<SelectProps>;
  type?: string;
  value?: Array<string | number | boolean> | string | number | boolean;
}

export interface StandardTextFieldProps extends BaseTextFieldProps {
  variant?: 'standard';
  InputProps?: Partial<StandardInputProps>;
  inputProps?: StandardInputProps['inputProps'];
}

export interface FilledTextFieldProps extends BaseTextFieldProps {
  variant: 'filled';
  InputProps?: Partial<FilledInputProps>;
  inputProps?: FilledInputProps['inputProps'];
}

export interface OutlinedTextFieldProps extends BaseTextFieldProps {
  variant: 'outlined';
  InputProps?: Partial<OutlinedInputProps>;
  inputProps?: OutlinedInputProps['inputProps'];
}

export type TextFieldProps = StandardTextFieldProps | FilledTextFieldProps | OutlinedTextFieldProps;

So I solved this problem as follows.
Although this is not the best policy, I feel that it is more a matter of operation as a team rather than a material-ui problem any more.
Include the component in a specific directory that extends material - ui.
Taking TextField as an example

src/components/extensions/TextFiled

import * as React from "react";
import {
  default as MUITextFiled,
  StandardTextFieldProps,
  FilledTextFieldProps,
  OutlinedTextFieldProps
} from "@material-ui/core/TextField";

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

export const StandardTextField: React.ComponentType<Omit<StandardTextFieldProps, "variant">> = props => (
  <MUITextFiled {...props} variant="standard" />
);
export const FilledTextField: React.ComponentType<Omit<FilledTextFieldProps, "variant">> = props => (
  <MUITextFiled {...props} variant="filled" />
);
export const OutlinedTextField: React.ComponentType<Omit<OutlinedTextFieldProps, "variant">> = props => (
  <MUITextFiled {...props} variant="outlined" />
);

From another program, we do not import import Material - UI 's TextField so we will import from this extensions . This eliminates TypeScript type problems.

The base problem is with type unions i.e. Foo | Bar and Omit. I don't think we can do much here beyond using any cast:

const StyledTextField: React.ComponentType<TextFieldProps> = styled(TextField)({}) as any;

Not sure if this defeats any type checking in styled though.

@eps1lon Solution appears to work for me. Final result was

import TextField, { TextFieldProps } from '@material-ui/core/TextField';

export const StyledTextField: React.ComponentType<TextFieldProps> = styled(TextField)`
` as any;

Thanks!

I think this should be related to https://github.com/DefinitelyTyped/DefinitelyTyped/issues/29832 and possibly https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30942

I also suggest, that this issue is not only causing problems for strict: true. In my typescript project it leaves material-ui completely incompatible to styled-components (after updating @types/styled-components to >=4.1.0). And all I use is strictNullChecks, noImplicitAny and noImplicitThis.

Every single component is affected.
e.g.

declare const Typography: React.ComponentType<TypographyProps>;
....
type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;

Please correct me here if this is a different problem. I will open a new issue then.

I encourage you to use

const StyledButton = styled(Button)`` as typeof Button;

This will remove any type information for props added by styled-components. However, most of these shouldn't be necessary anymore. Please let me know if there are any props from styled-components that are essential in your opinion.

There are both issues with the styled-component types (union props are lost: changing this caused a 20x perf regression in ts 3.4) and TypeScript design limitations (argument inference only considers one function overload).

@eps1lon by changing to not losing union props you are meaning switching from using Pick to using Pick distributed over union with extends any conditional clause? If so, can you provide an example of perf regression? I've monkey-patched my local styled-components definitions and as of TS 3.5 unable to notice any regression whatsoever — maybe TS team updated smth related in TS 3.5?

@radiosterne Sounded like it. The regression was dicovered in 3.4: https://github.com/microsoft/TypeScript/issues/30663

@eps1lon that's great; I take it we are now able to substitute Pick for a better version, or do you mind? I'll make a pull request to DefinitelyTyped, if you're ok with it.

@radiosterne Have you done some progress on the topic?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  Â·  3Comments

FranBran picture FranBran  Â·  3Comments

chris-hinds picture chris-hinds  Â·  3Comments

mb-copart picture mb-copart  Â·  3Comments

activatedgeek picture activatedgeek  Â·  3Comments