How to type React Components with TypeScript
source link: https://dmitripavlutin.com/typescript-react-components/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
In this post, I’m going to discuss why and how to use TypeScript to type React components.
You’ll find how to annotate component props, mark a prop optional, and indicate the return type.
1. Why typing React components?
TypeScript is useful if you’re coding middle and bigger size web applications. Annotating variables, objects, and functions creates contracts between different parts of your application.
Typing constrains how a certain variable, object, or function must be used.
For example, let’s say I am the author of a component that displays a formatted date on the screen.
interface FormatDateProps {
date: Date
function FormatDate({ date }: FormatDateProps): JSX.Element {
return <div>{date.toLocaleString()}</div>;
According to the FormatDateProps
interface, the component FormatDate
accepts date
prop only an instance of Date
. That is a constraint of how the component should be used.
Why is this constraint important? Because the FormatDate
component calls the method date.toLocaleString()
on the date instance.
Then the user of the FormatDate
component would have to satisfy the constraint, and provide date
prop only with Date
instances:
<FormatDate
date={new Date()}
/>
If the user forgets about the constraint, and for example provides a string "Sep 28 2021"
to date
prop:
<FormatDate
date="Sep 28 2021"
Type 'string' is not assignable to type 'Date'.Type 'string' is not assignable to type 'Date'.
/>
Then TypeScript will show a type error and indicate the expected data type.
2. Typing props
In my opinion, the best benefit React takes from TypeScript is the props typing.
Typing a React component is usually a 2 steps process.
A) Define the interface that describes what props the component accepts using an object type. A good naming convention for the
props interface is ComponentName
+ Props
= ComponentNameProps
B) Inside the functional component function, annotate the props parameter with the props interface.
For example, let’s annotate a component Message
that accepts 2 props: text
(a string) and important
(a boolean):
interface MessageProps {
text: string;
important: boolean;
function Message({ text, important }: MessageProps) {
return (
<div>
{important ? 'Important message: ' : 'Regular message: '}
{text}
</div>
MessageProps
is the interface that describes the type of props the component accepts: text
prop as string
, and important
as boolean
.
Now when rendering the component, you would have to set the prop values according to the props type:
<Message
text="The form has been submitted!"
important={false}
/>
Basic Prop Types suggests types for different kinds of props. Use the list as an inspiration.
2.1 Props validation
What’s great now is that if you happen to provide the component with the wrong set of props, or wrong value types, then TypeScript is going to warn you at compile time.
Usually, a bug is caught in one of the following phases — type checking, unit testing, integration testing, end-to-end tests, bug report from the user — and the earlier you catch the bug, the better!
If the Message
component renders with an invalid prop value:
<Message
text="The form has been submitted!"
important={0}
Type 'number' is not assignable to type 'boolean'.Type 'number' is not assignable to type 'boolean'.
/>
or without a prop:
<Message
Property 'text' is missing in type '{ important: true; }' but required in type 'MessageProps'.Property 'text' is missing in type '{ important: true; }' but required in type 'MessageProps'.
important={true}
/>
then TypeScript will warn about that.
When typing React props, be as restrictive as it makes sense.
2.2 children prop
children
is a special prop in React components: it holds the content between the opening and closing tag when the component is rendered: <Component>children</Component>
.
That’s why the most often content of the children
prop is the JSX element, which can be typed using a special type JSX.Element
(usually available globally in a React environment).
Let’s slightly change the Message
component to use the children
prop:
interface MessageProps {
children: JSX.Element | JSX.Element[];
important: boolean;
function Message({ children, important }: MessageProps) {
return (
<div>
{important ? 'Important message: ' : 'Regular message: '}
{children}
</div>
Take a look at the children
prop in the interface: it accepts a single element JSX.Element
or an array of element JSX.Element[]
.
Now you can use an element as a child to indicate the message:
<Messageimportant={false}>
<span>The form has been submitted!</span>
</Message>
or multiple children:
<Messageimportant={false}>
<span>The form has been submitted!</span>
<span>Your request will be processed.</span>
</Message>
Challenge: how would you update the MessageProps
interface to support also a simple string
value as a child? Write your solution in a comment below!
2.3 Optional props
To make a prop optional in the props interface, mark it with a special symbol symbol ?
.
For example, let’s mark the important
prop as optional:
interface MessageProps {
children: JSX.Element | JSX.Element[];
important?: boolean;
function Message({ children, important = false }: MessageProps) {
return (
<div>
{important ? 'Important message: ' : 'Regular message: '}
{children}
</div>
You can see that inside that MessageProps
interface the important
prop is marked with an ?
— important?: boolean
— making the prop optional.
Inside the Message
function I have also added a false
default value to the important
prop. That’s going to be the default value in case if important
prop is not indicated.
Now TypeScript allows you to skips the important
prop:
<Message>
<span>The form has been submitted!</span>
</Message>
Of course, you can still use important
if you’d like to:
<Messageimportant={true}>
<span>The form has been submitted!</span>
</Message>
3. Return type
In the previous examples Message
function doesn’t indicate explicitly its return type. That’s because TypeScript is smart and can infer the function’s return type — JSX.Element
:
type MessageReturnType = ReturnType<typeof Message>;
type MessageReturnType = JSX.Element
My recommendation is to enforce each function to explicitly indicate the return type. Many silly mistakes and typos can be caught by doing so.
In the case of React functional components the return type is usually JSX.Element
:
function Message({
children,
important = false
}: MessageProps): JSX.Element {
return (
<div>
{important ? 'Important message: ' : 'Regular message: '}
{children}
</div>
There are cases when the component might return nothing in certain conditions. If that’s the case, just use
a union JSX.Element | null
as the return type:
interface ShowTextProps {
show: boolean;
text: string;
function ShowText({ show, text }: ShowTextProps): JSX.Element | null {
if (show) {
return <div>{text}</div>;
return null;
ShowText
returns an element if show
prop is true
, otherwise returns null
. That’s why the ShowText
function’s return type is a union JSX.Element | null
.
4. Conclusion
React components can greatly benefit from TypeScript.
A great benefit of typing is the ability to validate the component props. Usually, that’s performed by defining an interface using an object type where each prop declares its type.
Then, when the annotated component renders, TypeScript verifies whether correct prop values were supplied.
On top of data validation, the types can be a great source of meta information that gives clues of how the annotated function or variable works.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK