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:
- You can take advantage of TypeScriptâs type inference to avoid explicitly defining component types, which means less and cleaner code.
-
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
, anddefaultProps
. - Limits you to use function expressions.
- Doesnât support generics.
- Complicates the types of Components with a Namespace.
- Has unnecessary (when using TypeScript) and legacy properties, like
- 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.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.
- 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 implicitchildren
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 theVoidFunctionComponent
/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 theuseContext
hook to consume context. - The type of this property (right side of the colon) defines that the component can only return a
ReactElement
ornull
, preventing it from returning invalid values, likeundefined
.
- The
- 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 aspropTypes
, but applied to thecontext
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.
- Beginners getting into typed React
- JavaScript codebases
- 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:
- TypeScript + React: Why I donât use React.FC by Stefan Baumgartner
- Should you use React.FC for typing React Components by Harry Mumford-Turner
- Why you probably shouldnât use React.FC to type your React components by Sam Hendrickx
- Not an article, but a good cheatsheet with some great tips: Function Components | React TypeScript Cheatsheets
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)