Announcing: A Free Book, A New Course, A Huge Price Cut...
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
When you're working in a React app, declaring a type for your React props is likely going to be your most common TypeScript activity.
There's some debate over the best method for typing props. In this article, I'll show you the pros and cons of each method and give you my recommendation.
If you want to learn by doing, check out my free interactive course on React and TypeScript.
Props can be declared using an inline object literal.
import { ReactNode } from "react";
const Wrapper = (props : {
children ?: ReactNode ;
}) => {
return <div >{props .children }</div >;
};
Prop types can also be destructured, leading to a strange {}: {}
syntax.
import { ReactNode } from "react";
const Wrapper = ({
children ,
}: {
children ?: ReactNode ;
}) => {
return <div >{children }</div >;
};
Prop types can also be extracted to a type alias:
import { ReactNode } from "react";
export type WrapperProps = {
children ?: ReactNode ;
};
const Wrapper = (props : WrapperProps ) => {
return <div >{props .children }</div >;
};
Type aliases should always be exported along with the component - that way you can use them in other files if needed.
Props declared in a type alias can also be destructured:
const Wrapper = ({ children }: WrapperProps ) => {
return <div >{children }</div >;
};
Interfaces are another way to declare props:
import { ReactNode } from "react";
export interface WrapperProps {
children ?: ReactNode ;
}
const Wrapper = (props : WrapperProps ) => {
return <div >{props .children }</div >;
};
Just like type aliases, interfaces should always be exported so they can be reused later.
And, just like type aliases, interfaces can be destructured.
const Wrapper = ({ children }: WrapperProps ) => {
return <div >{children }</div >;
};
Now that we've seen all the methods, let's look at all the pros and cons before we make our final decision.
The main benefit of inline object literals is speed. You can type your props quickly and move on.
The issue becomes that inline object literals will, eventually, need to be extracted out to a type.
So if you're writing a component, you should probably use a type alias or interface.
To get over this hurdle, I'd advise writing your own code snippets for type aliases and interfaces.
Here's a code snippet that will work in VSCode for creating your own components.
{
"component": {
"prefix": "comp",
"body": [
"export interface $1Props {",
" $2",
"}",
"",
"export const $1 = (props: $1Props) => {",
" return $3",
"}"
]
}
}
Autocompleting to comp
will expand to a component with an interface, both exported.
My opinions on type aliases vs. interfaces are well documented in this article. In short, I prefer the type
in general because of the 'declaration merging' property of the interface
, which can confuse and frustrate beginners.
But there's one particular case where interfaces win over type aliases - that's in creating complex intersections of props.
Let's say you're creating a component that has all the props of input
but needs to add a label
prop. You'll need to extend from the ComponentProps
type helper, described in my article.
It is tempting to use a type
alias:
import { ComponentProps } from "react";
export type InputProps =
ComponentProps <"input"> & {
label : string;
};
export function Input ({
label ,
...props
}: InputProps ) {
return (
<label >
{label }
<input {...props } />
</label >
);
}
But unfortunately, intersections used this way will, on the scale of a large codebase, slow TypeScript down.
Instead, you should use an interface, using interface extends
:
import { ComponentProps } from "react";
export interface InputProps
extends ComponentProps <"input"> {
label : string;
}
This advice comes from TypeScript's performance wiki, as well as a PR from Sentry's codebase that sped up their type checking by removing these intersections in favor of interfaces.
So in situations where you're extending from other types, use interfaces.
In general, you should use interface
to declare your props. They're the most performant, and with a code snippet, they're fast to write.
If you've already got props declared using type
or inline object literals, don't worry - this isn't worth a refactor.
But if your codebase starts to feel sluggish in your IDE, check for intersections in your props. If you find any, try to refactor them to interface extends
.
Share this article with your friends
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
Learn why the order you specify object properties in TypeScript matters and how it can affect type inference in your functions.
Learn how to use corepack
to configure package managers in Node.js projects, ensuring you always use the correct one.
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
Discover when it's appropriate to use TypeScript's any
type despite its risks. Learn about legitimate cases where any
is necessary.
Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.