SolvedDefinitelyTyped [@types/styled-components] Generics in functional components (and best practise for generics in general)

I'm trying to use style my functional components with generics, but cannot find any approach to reach that.

For class-components is working this workaround: styled-components/styled-components#1803 (comment)

but for functional one, I didn't find a way how to do that, like you can see in the example below.

import React from 'react';
import styled from 'styled-components';

let MyComponent: any;

type Props<T> = { a: T };

// with Functional doesn't work
const FunctionalComponent: <T>(p: Props<T>) => React.ReactElement<Props<T>> = props => (
  <MyComponent {...props} />
);
const StyledFunctional = styled(FunctionalComponent)`
  color: red;
`;
const StyledFunctionalRetyped = (styled(FunctionalComponent)`
  color: red;
` as React.ReactNode) as new <T>() => FunctionalComponent<T>;
//                                    -------------------
// => 'FunctionalComponent' refers to a value, but is being used as a type here.

// with Class component works fine
class ClassComponent<T> extends React.Component<Props<T>> {
  render() {
    return <MyComponent {...this.props} />;
  }
}
const StyledClassRetyped = (styled(ClassComponent)`
  color: red;
` as React.ReactNode) as new <T>() => ClassComponent<T>;

// example of usage
const Examples = (
  <>
    <StyledFunctional<string> a={8} />
    {/*               ------ => doesn't support generics (expected 0 arguments) */}
    <StyledFunctionalRetyped<string> a={8} /> {/* doesn't work*/}
    <StyledClassRetyped<string> a={8} /> {/* works properly (shows error) */}
  </>
);
26 Answers

✔️Accepted Answer

The generic argument for the wrapped component can be passed like <StyledFoo<FC<Props<Bar>>> ... /> to provide the same type safty as <Foo<Bar> ... /> does.

Base on the example provided by OP:

圖片

Now ts can tell that '8' is valid while 8 is invalid

P.s. I also use a similar workaround for antd/select

圖片

ValueType=number

Environment:

  • styled-components: 5.0.0
  • @types/styled-components: ^5.1.4

Other Answers:

Thanks @Naararouter !

Did some cleaning (e.g. removing StyledComponent) to find out core of the issue and it's all about wrapping the styled() component by a generic one, which handles the generics-typing.

import React from "react";
import styled from "styled-components";

type Props<T> = { a: T; className?: string; }

const GenericComponent: <T>(p: Props<T>) => 
    React.ReactElement<Props<T>> = ({ a, ...props }) => <div {...props}>{a}</div>;

// wrapped styled-component and re-typed it works as expected
const StyledGeneric = <T extends {}>(props: Props<T>) => {
  const StyledComponent = styled<React.FC<Props<T>>>(GenericComponent)`
    color: red;
  `;
  return <StyledComponent {...props} />;
};

export const Example = () => <StyledGeneric<number> a={8} />;

Warning: as @tokland found later, this solution is throwing a runtime error and the solution from @choznerol 👇 below looks much better (using <StyledFoo<FC<Props<Bar>>> ... />)

You are probably right @tokland and there is something wrong with my solution. Based on what the official documentation says: WARNING: THIS IS VERY VERY BAD AND SLOW, DO NOT DO THIS!!!

But @choznerol 's solution probably solves our struggles. See my PR for your code if you see fit: https://github.com/tokland/test-styled-components/pull/1/files

More Issues: