DEV Community

Cover image for How to use Components with generics types better
Michael De Abreu
Michael De Abreu

Posted on

How to use Components with generics types better

This is going to be a short post, but I really wanted to share a better way to use Typescript Generics better.

But first, how do you use Generics in Components right now? Maybe you don't, maybe you do, but you loose in the middle valuable type information. Maybe you are like me, and you are too waiting for TypeScript#22063 to be merge.

Suggestion: Type annotations and interfaces for function declarations #22063

Currently in TypeScript, function declarations cannot be typed in the same way as function expressions, e.g. this function can implement the React.FC interface:

interface TestProps {
    message: string
}

const Test: React.FC<TestProps> = ({ message }) => {
    return <div>{message}</div>
}

But this function can't, at least not directly:

function Test({ message }: TestProps) {
    return <div>{message}</div>
}

This becomes more of an issue if you try to add properties to the function object:

Test.defaultProps = {
    message: 'hi'
}

It seems that currently the only way to specify the type for the function object in the second example is to create a new variable:

const AliasForTest: React.FC<TestProps> = Test
AliasForTest.defaultProps = {
    message: 'hi'
}

This seems like kind of an ugly workaround, so it seems that the current idiom is to just prefer arrow functions for cases like this. But this leads to inconsistency on teams that generally prefer function declarations over const expressions for top-level functions. Personally I find it more readable to see the word "function" for top-level functions rather than seeing "const", which is generally already all over the place in the code. There is even an ESLint rule for teams that share my preference (although I don't think it's been ported to TSLint yet): https://eslint.org/docs/rules/func-style. In any case, I have seen others express similar views and other codebases (including some from Facebook and Apollo, for example) that still prefer the "function" keyword for top-level functions.

However, stylistically it's also a problem if top-level functions are declared some places as declarations (using function) and in other places as expressions (using const). But for those who desire consistency, TypeScript is basically forcing the use of expressions, due to the issues described above.

This is far from being a top priority of course, but I was surprised to see that TypeScript didn't provide some equivalent typing syntax for function declarations. It would be great if this could be considered for a future version (even if far in the future). Thanks for reading!

What if I tell you there is a better way to type generic components without loosing either the generic nor the component information?

Snipped

export type Component = (<Data>(props: ComponentProps<Data>) => ReturnType<FC>) & VFC<ComponentProps<{}>>;
Enter fullscreen mode Exit fullscreen mode

This piece of code, is an overload. Just a plain old overload can do the job of having both, the generic, and the component properties.

I really didn't think about this until I saw the last comment of the issue I mentioned earlier, and the answer to that comment.

You can use an interface if you like it

export interface Component extends VFC<ComponentProps<{}>> { <Data>(props: ComponentProps<Data>): ReturnType<FC> };
Enter fullscreen mode Exit fullscreen mode

When consuming, you need to declare a component that fulfil both overloads, like so:

export const Component: Component = ({ data, keys }: ComponentProps<Record<any, any>>) => {
  ...
}
Enter fullscreen mode Exit fullscreen mode

Playground example

TS Playground

That's all folks!

Did you know about this? Don't lie to me, I've been working with TS for 5 years and I never though about this, and let me tell you, this is not something just to show at a Playground, I have now a working Table component using this generic declaration, checking all the properties inside the data and the description around the table.

Let me know in the comments what other techniques do you use to deal with generics in React.

Image by NickyPe from Pixabay

Discussion (0)