DEV Community

Cover image for Clean up your React component types 🧼
JosĂŠ Pereira
JosĂŠ Pereira

Posted on • Edited on

Clean up your React component types 🧼

In this article, we go into some practices you can follow in your React codebase with Function Components and TypeScript to cleanly type your components, and hopefully write less code that serves the same purpose.

ℹ️ Let me start by saying that this article is opinionated and it’s not trying to define what are the correct practices.

TL;DR

Here’s a quick summary of this article, if you’re in a hurry:

  1. You can take advantage of TypeScript’s type inference to avoid explicitly defining component types, which means less and cleaner code.
  2. React.FC should not be used when defining components on codebases with Function Components and TypeScript, because it:
    • Has unnecessary (when using TypeScript) and legacy properties, like propTypes, contextTypes, and defaultProps.
    • Limits you to use function expressions.
    • Doesn’t support generics.
    • Complicates the types of Components with a Namespace.
  3. Cleaner component types make your code more future-proof and decoupled.

1. Let TypeScript do the work 💪

You might’ve heard that TypeScript offers type inference. This means that we don’t need to explicitly define the type of a variable, as long as it can be inferred from its value. It’s a simple concept that can help clean your codebase of unnecessary explicit types.

Applied to React components, it’s arguably cleaner to let TypeScript infer the component’s return type than it is to explicitly define it, as portrayed by the following snippet.

You might argue that explicitly defining your return type is safer, which is true, so you should always be a little more careful. When using type inference, you should always check the inferred type to avoid making mistakes.

💡 Tip — Enable or disable the following eslint rule to be forced to add an explicit return type or not: typescript-eslint/explicit-function-return-type


A brief history recap before the next topic. From 2013 to 2019, React components were written using classes, that would extend other built-in ones, like React.Component and React.PureComponent, to make up a valid component. But, since the release of hooks, in v16.8, we’ve transitioned to write components as functions, which is much easier, because you write less, and everything is still there.

Function Components are rather simple, as they don’t require anything to be considered a component, other than returning valid component values, like JSX. Although, quite a few people still feel the need to mark their functions as components. This is frequently done by using the React.FCtype from the @types/react package. The thing about this type is that it brings old and unnecessary properties, some of them from class-based React, to your otherwise clean function components. How do we solve this?

2. Stop using React.FC

Types like React’s FunctionComponent/FC and VoidFunctionComponent/VFC were introduced to ease the creation of function components, while still having some useful properties, like children, and other internal ones, like propTypes, contextTypes, defaultProps, and displayName.

1.1. But do we need all these properties? 🤔

Well, to answer that, let’s dissect the FC type for example, which is undoubtedly the most used one.

  • 1️⃣ (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null — This first property defines the parameters and return type of the function component itself. Let’s take a closer look at it:
    • The props parameter on the function corresponds to the properties that it accepts (defined by the user) plus an implicit children property. This is misleading, as a lot of components don’t accept children. If you’re not doing anything with that property, as of @types/react v16.9.48, you can use the VoidFunctionComponent/VFC type instead, which does the same, but doesn’t add it to your type.
    • The context parameter is used to pass context to descendant components. When using hooks, this property is unnecessary, as we can and should resort to the useContext hook to consume context.
    • The type of this property (right side of the colon) defines that the component can only return a ReactElement or null, preventing it from returning invalid values, like undefined.
  • 2️⃣ propTypes — Allows the assignment of data types to the component’s properties, to be able to add type checking to JavaScript components. As you might expect, this isn’t useful when using TypeScript, as we can already define our types, which it also accepts as a parameter: FC<MyType>.
  • 3️⃣ contextTypes — Has the same function as propTypes, but applied to the context passed to the component (mentioned above on the first property). This was used in JavaScript classes with legacy context.
  • 4️⃣ defaultProps — Allows the assignment of a default value to the properties accepted by the component. Useful for classes, but when using function components, we’re able to use ES6’s default parameters instead:
  • 5️⃣ displayName — Allows the user to assign an explicit name to a component, to be used in debugging messages. By default, the name is inferred from the name or the function or class that defined the component.

1.2. Limitations of React.FC 👎

What was mentioned above is not very disruptive, but rather a matter of using cleaner types. Although, there are other more limiting downsides of the FC type.

A. Forces you to type the function, not the props

When using FC, you need to type the function, not its props, which forces you to use function expressions, as function declarations cannot be typed.

Here’s what I mean:

This limitation prevents you, for example, from freely positioning your functions, as function expressions are not hoisted, and cannot be used before their definition.

B. Doesn’t support generics

If you want to write a function component with generic types, FC won’t work for you, as there’s no valid syntax to do so.
For example, let’s consider the following component:

When using FC, where do we define the generic?

This causes inconsistencies in your component definitions, as some of them will be using React.FC, and the ones that accept generic types, will not.

C. Requires more code to create a Component with a Namespace

This “pattern” is commonly used in React, and it’s fairly easy to implement using regular functions.

But writing the code above with FC requires you to explicitly define all your types on the namespace component, which increases the boilerplate.

This is not a major downside, but rather a small sacrifice in simplicity.

1.3. What are the alternatives to React.FC?

The FC type adds everything you need for a component to work, which is one of the main reasons it’s widely used. But it’s easy to achieve the same results with cleaner types, by writing some utility types for yourself.

The following are some alternatives that allow you have some of the properties offered by FC.

A. children

If our component accepts children, there are multiple ways to type it for such:

  • 1️⃣ Using helper types from@types/react

This package allows you to add this property to your custom type by using the PropsWithChildren utility.

The only issue with this type is that it requires an argument, so if you have a component that only needs children as it’s props, it doesn’t allow you to do the following: (props: PropsWithChildren) ❌

  • 2️⃣ Defining the property in your type

There’s always the possibility of defining the property in your custom type.

  • 3️⃣ Define your own utility type

Don’t want to type it every time? That’s okay, I’m lazy too.

B. displayName

The displayName might also be useful for debugging, so if you wish to override it, do it just like you normally would.

No need to define the property in your component type, as TypeScript will infer it 🚀

C. What about the other React.FC properties?

I’m confident that you don’t need them. Nowadays, if you’re using Function Components with TypeScript on your codebase, unless you’re doing some workaround, you won’t need properties like propTypes, contextTypes, or defaultProps. If you do, feel free to leave a comment with your use case, I’m always open to learning and discussing this topic.

1.5. Is there any benefit to using React.FC?

Types such as FunctionComponent/FC and VoidFunctionComponent/VFC have nothing wrong on their own. If you’re indifferent to their limitations, they can be great in the following scenarios.

  1. Beginners getting into typed React
  2. JavaScript codebases
  3. Legacy codebases that use class-based components or legacy context code

Why does this matter?

You might argue that improving your component types is irrelevant because, in the end, everything is transpiled to JavaScript and the code won’t run faster because of it. While that’s true, I believe these small changes have a couple of benefits.

  • 🚀 Increases developer experience and code readability.
  • 🧼 Promotes clean code practices and the use of built-in JavaScript & TypeScript functionalities.
  • 💪 Strengthens developer knowledge of TypeScript and React’s internal mechanisms. Quite a few people are unaware of the reasons behind using React.FC.
  • 🧩 Makes your code more future-proof and decoupled. This means that if, for any reason, a built-in type changes, you have less chance of being affected by it. Also, if you wish to move to another React-based alternative (like preact), the process is easier, as you are not coupled to the @types/react package (more info about this in this great article).

Nonetheless, this is not just my personal opinion, as back in early 2020, React.FC was removed from the official create-react-app, for some of the reasons mentioned above.

If you wish to remove all instances of React.FC from your codebase, you can use this jscodeshift codemod (Tip: Use npx).

📚 There are also great resources and articles discussing this, from which I based myself on. Be sure to give them a read:


Thank you for reading, I hope you learned something from this article, I know I did 🚀

You can also find this article on Medium.

Top comments (0)