Clean up your React component types 🧼
Tags: typescript, react, webdev, programming
Published: 2022-01-04 14:16:24


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:

2. You can take advantage of TypeScript’s type inference to avoid explicitly defining component types, which means less and cleaner code.
3. `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.
4. 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**](https://www.typescriptlang.org/docs/handbook/type-inference.html). 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.

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=component-ts-inference.tsx %}

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](https://reactjs.org/docs/introducing-jsx.html). Although, quite a few people still feel the need to mark their functions as components. This is frequently done by using the `React.FC `type 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](https://github.com/search?l=TSX&q=React.FC&type=Code).

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=function-component.tsx %}

- 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`](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/46643), 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**](https://reactjs.org/docs/context.html) 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`](https://reactjs.org/docs/typechecking-with-proptypes.html) — 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`](https://reactjs.org/docs/typechecking-with-proptypes.html#default-prop-values) — Has the same function as `propTypes`, but applied to the [`context`](https://reactjs.org/docs/context.html) passed to the component (mentioned above on the first property). This was used in JavaScript classes with legacy context.
- 4️⃣ [`defaultProps`](https://reactjs.org/docs/typechecking-with-proptypes.html#default-prop-values) — 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters
) instead:
{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=javascript-es6-default-parameters.tsx %}
- 5️⃣ [`displayName`](https://reactjs.org/docs/react-component.html#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:
{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=react-fc-function-declarations.tsx %}

This limitation prevents you, for example, from freely positioning your functions, as [function expressions are not hoisted](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function#function_expression_hoisting), 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:

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=generic-function-components.tsx %}

When using `FC`, where do we define the generic?

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=react-fc-no-generic-support.tsx %}

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”](https://medium.com/@kunukn_95852/react-components-with-namespace-f3d169feaf91) is commonly used in React, and it’s fairly easy to implement using regular functions.

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=component-with-namespace.tsx %}

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

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=react-fc-component-with-namespace.tsx %}

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.

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=props-with-children-component.tsx %}

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.

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=children-explicit-property.tsx %}

- **3️⃣ Define your own utility type**

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

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=children-custom-helper.tsx %}

### B. displayName

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

{% gist https://gist.github.com/jomifepe/98e4622e060113e9580af4bdd2dd53c0 file=display-name-override.tsx %}

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](https://fettblog.eu/typescript-react-why-i-dont-use-react-fc/#3.-easier-to-move-to-preact)).

Nonetheless, this is not just my personal opinion, as back in early 2020, `React.FC` [was removed from the official create-react-app](https://github.com/facebook/create-react-app/pull/8177), 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](https://github.com/gndelia/codemod-replace-react-fc-typescript) (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:

- [TypeScript + React: Why I don’t use React.FC](https://fettblog.eu/typescript-react-why-i-dont-use-react-fc/) by Stefan Baumgartner
- [Should you use React.FC for typing React Components](https://www.harrymt.com/blog/2020/05/20/react-typescript-react-fc.html) by Harry Mumford-Turner
- [Why you probably shouldn’t use React.FC to type your React components](https://medium.com/raccoons-group/why-you-probably-shouldnt-use-react-fc-to-type-your-react-components-37ca1243dd13) by Sam Hendrickx
- Not an article, but a good cheatsheet with some great tips: [Function Components | React TypeScript Cheatsheets](https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components/)

---

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

You can also find this article on [Medium](https://medium.com/xgeeks/clean-up-your-react-component-types-45acec85d4c3).