Using TypeScript together with React has proven to be a powerful combination.
Some people are afraid to move to TypeScript because they think it forces you to write a lot of boilerplate code. In my experience, once these people give TypeScript a try, they won't go back to writing Vanilla JavaScript again.
I want to take this fear from you by showing how you can easily define your React components with TypeScript in this article.
Functional components with TypeScript
You can create functional components in TypeScript just like you would in JavaScript.
The main difference is the FC
interface, which stands for Function Component. We use this to tell TypeScript that this is a React function component and not just a regular function.
Optionally, we can add types for the props by defining an interface and passing it to the generic FC
.
A functional component then typically looks like this:
import React, { FC } from 'react';
interface TitleProps {
title: string;
}
const Title: FC<TitleProps> = ({ title, subtitle }) => {
return (
<>
<h1>{title}</h1>
<h2>{subtitle}</h2>
</>
);
};
export default Title;
If we now use this component somewhere in our application, we get to reap the benefits of this:
We get autocompletion and compilation errors in our editor that warn us when we forgot a property or use the wrong types.
Generic types in TypeScript are like functions that accept parameters. Instead of parentheses, generic types use angle brackets (
<>
).For the
FC
interface, you don't need to pass any parameters, so you could use it without a generic type by omitting the angle brackets altogether when your component doesn't accept any props:const Title: FC = () => { // ... }
Optional props
Not all of your props need to be required. We can make a prop optional by adding a question mark to the interface:
interface TitleProps {
title: string; // required
subtitle?: string; // optional
}
This will allow you to omit the prop when you don't need it.
Alternatives to the FC interface
If you already know TypeScript, you might have wondered why I'm not applying the prop interface to the function parameters itself.
const Title = ({ title, subtitle }: TitleProps) => {
// ...
}
The syntax above works fine as well. In fact, many people write function components like this in TypeScript.
However, the reason why you might want to use a generic type like FC
is that this comes with all the typings that you could possibly need for a function component.
This includes, for example, the implicit children
property. Being a default prop of any React component, we don't need to add it to our interface.
import React, { FC } from 'react';
interface TitleProps {
title: string;
subtitle?: string;
}
const Title: FC<TitleProps> = ({ title, subtitle, children }) => { return (
<>
<h1>{title}</h1>
<h2>{subtitle}</h2>
<div>{children}</div>
</>
);
};
export default Title;
This can be seen as a disadvantage though. Imagine you don't want your component to accept children. The FC
interface always adds the children prop, regardless of whether you use it or not.
It would be better to define the props without React's generic interface and use FC
in the case you want to access the children property.
FC vs FunctionComponent
I've come across the FC
and the FunctionComponent
interfaces and wondered what advantages one had over the other.
A quick look at React's type definitions revealed that FC
is just the shorthand for FunctionComponent
. Both refer to the same interface.
type FC<P = {}> = FunctionComponent<P>;
Type-safe state in function components
To have type-safety for the state in functional components, we don't necessarily need to modify our code: The useState
hook inherits the type from the value we use to initialize it.
If we use a more complex type or don't initialize the state, we can pass the type like the following:
const [title, setTitle] = useState<string>(null);
Class components in TypeScript
Although I default to writing functional components, some cases require me to work with class components as well.
Let's jump right into it and have a look at the previous example as a class component in TypeScript.
import React, { Component } from 'react';
interface TitleProps {
title: string;
subtitle?: string;
}
class Title extends Component<TitleProps> {
render() {
const { title, subtitle, children } = this.props;
return (
<>
<h1>{title}</h1>
<h2>{subtitle}</h2>
<div>{children}</div>
</>
);
}
}
export default Title;
As we can see here, we extend React's Component
class just like we would when working with JavaScript.
However, this class interface is generic, so we can pass an interface to it that contains the types of our props Component<TitleProps>
.
Type-safe state in class components
We can also make our state type-safe by creating an interface for it and passing it as a parameter to Component
:
import React, { Component } from 'react';
interface TitleProps {
title: string;
subtitle?: string;
}
interface TitleState { counter: number;}
class Title extends Component<TitleProps, TitleState> { // ...
}
export default Title;
Constructors
We'll typically use constructors to initialize our class component's state.
Unfortunately, the React class won't know the type of our props automatically, which is why we need to specify the type of the props
parameter in the function call:
constructor(props: TitleProps) { super(props);
this.state = {
counter: 0,
};
}
If we don't do this and we run our code in strict mode, TypeScript will throw a compilation error.
Default props
We can define the default values of our properties by adding the static variable defaultProps
to our class component:
interface TitleProps {
title: string;
subtitle?: string;
}
class Title extends Component<TitleProps, TitleState> {
public static defaultProps = { title: 'Hey there!', };
//...
}
The React component is smart enough to inherit the prop types.
In the example, title
is a required property, however, since we've defined a default prop, it automatically becomes optional.
TypeScript won't throw an error if we don't add a title
prop in this case, even though it's required in our interface.
Conclusion
As you can see, using TypeScript for your React isn't too complicated and the amount of code you need to write is limited.
In exchange for this bit of extra effort, you get rewarded with type-safety and autocompletion, which results in a more solid code-base as it helps you to catch bugs early on during development.
If you want to learn more about how to use TypeScript with React, make sure to subscribe to my newsletter, so you don't miss my future articles.