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 error with nested styled, themed components #824

Closed
gargantuan opened this issue May 24, 2017 · 22 comments
Closed

Typescript error with nested styled, themed components #824

gargantuan opened this issue May 24, 2017 · 22 comments

Comments

@gargantuan
Copy link

gargantuan commented May 24, 2017

Version

1.0.5

Reproduction

Not sure how to replicate this in webpackbin

Steps to reproduce

Create a theme file, as per the typescript guidance here

import * as styledComponents from 'styled-components';
import { ThemedStyledComponentsModule } from 'styled-components';

interface MyTheme {
  backgroundColor: string;
}

const defaultTheme: MyTheme = {
  backgroundColor: blue;
};

const {
  default: styled,
  css,
  injectGlobal,
  keyframes,
  ThemeProvider,
} = styledComponents as ThemedStyledComponentsModule<MyTheme>;

export default styled;
export { css, injectGlobal, keyframes, ThemeProvider, MyTheme, defaultTheme };

Create two styled components.

  • The inner component wraps an input element.
  • The outer component wraps the inner component.
  • Expose the input elements API to the outer component using the {...others} = props pattern.
import * as React from 'react';
import styled, { defaultTheme } from './theme';

// Inner Component
interface IInnerComponentProps extends React.HTMLProps<HTMLInputElement> {
  foo?: string;
}

const InnerComponent: React.StatelessComponent<IInnerComponentProps> = props => {
  const {foo, ...others} = props;
  return <input type="text" {...others}/>;
};

const InnerStyled = styled(InnerComponent)`
  background: red;
`;

InnerStyled.defaultProps = defaultTheme;

// Outer component
interface IMyComponentProps extends React.HTMLProps<HTMLElement> {
  bar?: string;
}

const MyComponent: React.StatelessComponent<IMyComponentProps> = props => {
  const {bar, ...others} = props;
  return <div><InnerStyled type="text" {...others}/></div>;
};

const MyComponentStyled = styled(MyComponent)`
  color: green;
`;

MyComponentStyled.defaultProps = {
  theme: defaultTheme,
};

Expected Behavior

the line return <div><InnerStyled type="text" {...others}/></div>; should not generate a typescript error.

Actual Behavior

The following typescript error is generated

Type '{ value: string; ghostValue?: string; theme?: any; defaultChecked?: boolean; defaultValue?: strin...' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<ThemedOuterStyledProps<IGhostedTextInput...'.
  Type '{ value: string; ghostValue?: string; theme?: any; defaultChecked?: boolean; defaultValue?: strin...' is not assignable to type 'IntrinsicClassAttributes<Component<ThemedOuterStyledProps<IGhostedTextInputProps, IGhostedTextInp...'.
    Types of property 'ref' are incompatible.
      Type 'Ref<HTMLInputElement>' is not assignable to type 'Ref<Component<ThemedOuterStyledProps<IGhostedTextInputProps, IGhostedTextInputTheme>, ComponentSt...'.
        Type '(instance: HTMLInputElement) => any' is not assignable to type 'Ref<Component<ThemedOuterStyledProps<IGhostedTextInputProps, IGhostedTextInputTheme>, ComponentSt...'.
          Type '(instance: HTMLInputElement) => any' is not assignable to type '(instance: Component<ThemedOuterStyledProps<IGhostedTextInputProps, IGhostedTextInputTheme>, Comp...'.
            Types of parameters 'instance' and 'instance' are incompatible.
              Type 'Component<ThemedOuterStyledProps<IGhostedTextInputProps, IGhostedTextInputTheme>, ComponentState>' is not assignable to type 'HTMLInputElement'.
                Property 'accept' is missing in type 'Component<ThemedOuterStyledProps<IGhostedTextInputProps, IGhostedTextInputTheme>, ComponentState>'. 
@gargantuan gargantuan changed the title Typescript error with nested styled components Typescript error with nested styled, themed components May 24, 2017
@gargantuan
Copy link
Author

Is this a bug? And if so, is it a bug with Styled Components, or with Typescript?

@kitten
Copy link
Member

kitten commented May 30, 2017

It's not a bug :) you're using the default HTMLProps and trying to pass a ref, but unfortunately the ref is now pointing to an instance of the styled component and not the underlying element ref.

You'll either want to add a new type for ref, or more likely use innerRef instead

cc @styled-components/typers

@sergeyzwezdin
Copy link

Even more simple example generates compile-time error in TypeScript:

const LogoBlock = styled.div`
  background-color: lime;`;

const HeaderBlock = styled.div`
  border: 1px solid #333;

  > ${LogoBlock} {
    background: red;
  }
`;

It gave me following error:

error TS2345: Argument of type 'ComponentClass<ThemedOuterStyledProps<HTMLProps<HTMLDivElement>, any>>' is not assignable to parameter of type 'Interpolation<ThemedStyledProps<HTMLProps<HTMLDivElement>, any>>'.
  Type 'ComponentClass<ThemedOuterStyledProps<HTMLProps<HTMLDivElement>, any>>' is not assignable to type 'ReadonlyArray<string | number | InterpolationFunction<ThemedStyledProps<HTMLProps<HTMLDivElement>...'.
    Property 'find' is missing in type 'ComponentClass<ThemedOuterStyledProps<HTMLProps<HTMLDivElement>, any>>'.

Any ideas how to handle this?

@cesalberca
Copy link

It's been addressed in #837 and #882. However I'm still having problems when I updated styled-components to v2.0.1. I'm creating an issue if I'm not able to solve them.

@patrick91
Copy link
Contributor

@sergeyzwezdin the issue you've found has been fixed in v2.0.1
@gargantuan's issue is still unfixed, maybe @Igorbek can have a look at it.

@cesalberca please let us know if you have other issues, I haven't had too much time to play with v2 and typings :)

@kitten kitten closed this as completed Jun 8, 2017
@gargantuan
Copy link
Author

Can I ask why this issue has been closed? @patrick91's comment seems to be saying that it's unfixed.

@kitten
Copy link
Member

kitten commented Jun 8, 2017

@gargantuan sorry, I misread his comment :)

@kitten kitten reopened this Jun 8, 2017
@rohmanhm
Copy link

rohmanhm commented Jul 17, 2017

Yep, there's still no fixed.

But, I have fixed my issue by creating new React.Component

const ButtonDefault = styled(Button)`
  background-color: ${ (props) => props.intent ? props.intent : Color.blue } !important;
`

export default class extends React.Component<React.HTMLAttributes<{}> & typeof ButtonDefault.propTypes, {}> {
  public render () {
    return <ButtonDefault {...this.props}/>
  }
}

@Igorbek
Copy link
Contributor

Igorbek commented Jul 17, 2017

@gargantuan this should work:

function toInnerRef<T>(ref: undefined | React.Ref<T>): undefined | ((instance: T | null) => void) {
    if (!ref) {
        return undefined;
    }
    if (typeof ref === 'string') {
        throw new Error('name refs are not supported.');
    }
    return ref;
}

const MyComponent: React.StatelessComponent<IMyComponentProps> = props => {
    const { bar, ref, ...others } = props;
    const innerRef = toInnerRef(ref);
    return <div><InnerStyled type="text" {...others} innerRef={innerRef} /></div>;
};

Note, that ref from HTMLProps and ref from StyledComponent are not covariantly compatible.
styled-components use innerRef to reach out the ref of its inner component.

@goldins
Copy link

goldins commented Oct 17, 2017

Getting a similar issue when using polished 1.8.0 and styled-component 2.2.1:

import { ellipsis } from 'polished'; 

const Heading = styled.div`
  ${ellipsis};
`;

Results in:

error TS2345: Argument of type '(width?: string | number | undefined) => Object' is not assignable to parameter of type 'Interpolation<ThemedStyledProps<(ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>...'.
  Type '(width?: string | number | undefined) => Object' is not assignable to type 'ReadonlyArray<string | number | StyledComponentClass<any, any, any> | InterpolationFunction<Theme...'.
    Property 'concat' is missing in type '(width?: string | number | undefined) => Object'.

ellipsis's definition returns Object, which may be incompatible with styled-components, css in JS, or tagged template literals in general. Creating my own method that returns any works, but I'm not sure what the correct type should be.

Wrapping ellipsis like so:

export const EllipsisWrapper = (width: string | number): any => ellipsis(width);

Causes other errors such as:

error TS2322: Type '{ children: string; }' is not assignable to type '(IntrinsicAttributes & IntrinsicClassAttributes<Component<ThemedOuterStyledProps<(FlexChildProps ...'.
  Type '{ children: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<ThemedOuterStyledProps<(FlexChildProps &...'.
    Type '{ children: string; }' is not assignable to type 'Readonly<FlexChildProps & TextAlignInterface & HTMLProps<HTMLElement> & { theme?: any; innerRef?:...'.
      Property 'toFixed' is missing in type '{ children: string; }'.

@Igorbek
Copy link
Contributor

Igorbek commented Oct 17, 2017

If ellipsis defined as (width?: string | number | undefined) => Object then it indeed incorrect argument. An interpolation function accepts an object that consists of props and theme. It can accept neither width nor number.

The fix should be something like this:

const Heading = styled.div`
  ${(props: { width: number; }) => ellipsis(props.width)};
`;

<Header width={12} />; // usage
  • The interpolation now is a proper function taking props
  • The type of extra prop (width) was explicitly provided

@goldins
Copy link

goldins commented Oct 18, 2017

@Igorbek thanks for the reply. I was indeed missing that extraction step for width, but I think the issue is with the Object return type, not arguments.

The Heading styled-component you posted returns this error:

error TS2345: Argument of type '(props: { width: number; }) => Object' is not assignable to parameter of type 'Interpolation<ThemedStyledProps<HeadingProps, ThemeInterface>>'.
  Type '(props: { width: number; }) => Object' is not assignable to type 'ReadonlyArray<string | number | StyledComponentClass<any, any, any> | InterpolationFunction<Theme...'.
    Property 'find' is missing in type '(props: { width: number; }) => Object'.

Writing my own function with an implicit return type such as:

export const Ellipsis = () => ({
  textOverflow: 'ellipsis',
  overflow: 'hidden',
  whiteSpace: 'nowrap'
});

returns a similar typescript error:

error TS2345: Argument of type '() => { textOverflow: string; overflow: string; whiteSpace: string; }' is not assignable to parameter of type 'Interpolation<ThemedStyledProps<HeadingProps, ThemeInterface>>'.
  Type '() => { textOverflow: string; overflow: string; whiteSpace: string; }' is not assignable to type 'ReadonlyArray<string | number | StyledComponentClass<any, any, any> | InterpolationFunction<Theme...'.
    Property 'find' is missing in type '() => { textOverflow: string; overflow: string; whiteSpace: string; }'.

explicitly typing the return as Object causes:

error TS2345: Argument of type '() => Object' is not assignable to parameter of type 'Interpolation<ThemedStyledProps<HeadingProps, ThemeInterface>>'.
  Type '() => Object' is not assignable to type 'ReadonlyArray<string | number | StyledComponentClass<any, any, any> | InterpolationFunction<Theme...'.
    Property 'find' is missing in type '() => Object'.

Typing the return as any is a temporary work around, but I'm not sure what the correct return type should be, or if I'm missing something in between that converts the Object to a type that the interpolation function is compatible with.

Again, thanks for your help!

@Igorbek
Copy link
Contributor

Igorbek commented Oct 18, 2017

Ah, I forgot about this bit. Returning an object in interpolation is not allowed at the moment. If you intend to return a style object, then it is something we're going to support very soon, see #1123

@goldins
Copy link

goldins commented Oct 18, 2017

Great, thanks!

@LennardWesterveld
Copy link

LennardWesterveld commented Nov 10, 2017

HI all,

Just wondering if i have the same issue related to this one.
I have created a components like:

import * as React from 'react';
import {MouseEventHandler} from 'react';


interface IProps {
  className?: string;
  onClick?: MouseEventHandler<IProps>;
  menuMachineName: string;
}

class Nav extends React.Component<any, IProps> {
  
  public render() {
    const {onClick, menuMachineName} = this.props;
    // some magic to get the right menu trough a api.   
    return (menuMachineName);
  }
}

export {Nav}

And now i want to style the component with styled like this:

import {Nav} from './Nav';
const StyledNav = styled(Nav)`
   color: red;
`
render() {
    return (<div><StyledNav menuMachineName="main" /></div>);
}

And then i got this error:
TS2559: Type '{ menuMachineName: "main"; }' has no properties in common with type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<ThemedOuterStyledProps<WithOptionalTheme...'.

Typescript: ^2.6.1
react: ^16.1.0
styled-components: ^2.2.3

@Igorbek
Copy link
Contributor

Igorbek commented Nov 10, 2017

class Nav extends React.Component<any, IProps> {
                                  ^^^ here should be props

@LennardWesterveld
Copy link

Hi Igorbek,
Thank you! 👍 My bad i don't know why i did that

@mxstbr
Copy link
Member

mxstbr commented Dec 15, 2017

Closing since the original issue was answered by @Igorbek!

@mxstbr mxstbr closed this as completed Dec 15, 2017
@mulholo
Copy link

mulholo commented Sep 10, 2019

Sorry to reopen an old issue, but still running into issues with this

@Igorbek 's solution didn't work for me
#824 (comment)

@kerryboyko
Copy link

I'm still running into this issue.
JSX element type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)>) | (new (props: any) => Component<any, any, any>)>' is not a constructor function for JSX elements.

@mcabrams
Copy link

@gargantuan this should work:

function toInnerRef<T>(ref: undefined | React.Ref<T>): undefined | ((instance: T | null) => void) {
    if (!ref) {
        return undefined;
    }
    if (typeof ref === 'string') {
        throw new Error('name refs are not supported.');
    }
    return ref;
}

This currently generates the following type error:

      TS2322: Type '((instance: T | null) => void) | RefObject<T>' is not assignable to type '((instance: T | null) => void) | undefined'.
  Type 'RefObject<T>' is not assignable to type '(instance: T | null) => void'.
    Type 'RefObject<T>' provides no match for the signature '(instance: T | null): void'.

@julioflima
Copy link

This solves my problem.

https://stackoverflow.com/questions/52404958/using-styled-components-with-typescript-prop-does-not-exist

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

No branches or pull requests