Typing Component Props

This is intended as a basic orientation and reference for React developers familiarizing with TypeScript.

Basic Prop Types Examples

A list of TypeScript types you will likely use in a React+TypeScript app:

type AppProps = {
message: string;
count: number;
disabled: boolean;
/** array of a type! */
names: string[];
/** string literals to specify exact string values, with a union type to join them together */
status: "waiting" | "success";
/** any object as long as you dont use its properties (NOT COMMON but useful as placeholder) */
obj: object;
obj2: {}; // almost the same as `object`, exactly the same as `Object`
/** an object with any number of properties (PREFERRED) */
obj3: {
id: string;
title: string;
};
/** array of objects! (common) */
objArr: {
id: string;
title: string;
}[];
/** a dict object with any number of properties of the same type */
dict1: {
[key: string]: MyTypeHere;
};
dict2: Record<string, MyTypeHere>; // equivalent to dict1
/** any function as long as you don't invoke it (not recommended) */
onSomething: Function;
/** function that doesn't take or return anything (VERY COMMON) */
onClick: () => void;
/** function with named prop (VERY COMMON) */
onChange: (id: number) => void;
/** alternative function type syntax that takes an event (VERY COMMON) */
onClick(event: React.MouseEvent<HTMLButtonElement>): void;
/** an optional prop (VERY COMMON!) */
optional?: OptionalType;
};

Notice we have used the TSDoc /** comment */ style here on each prop. You can and are encouraged to leave descriptive comments on reusable components. For a fuller example and discussion, see our Commenting Components section in the Advanced Cheatsheet.

Useful React Prop Type Examples

Relevant for components that accept other React components as props.

export declare interface AppProps {
children1: JSX.Element; // bad, doesnt account for arrays
children2: JSX.Element | JSX.Element[]; // meh, doesn't accept strings
children3: React.ReactChildren; // despite the name, not at all an appropriate type; it is a utility
children4: React.ReactChild[]; // better, accepts array children
children: React.ReactNode; // best, accepts everything (see edge case below)
functionChildren: (name: string) => React.ReactNode; // recommended function as a child render prop type
style?: React.CSSProperties; // to pass through style props
onChange?: React.FormEventHandler<HTMLInputElement>; // form events! the generic parameter is the type of event.target
// more info: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring
props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
}
Small `React.ReactNode` edge case

This code typechecks but has a runtime error:

type Props = {
children: React.ReactNode;
};
function Comp({ children }: Props) {
return <div>{children}</div>;
}
function App() {
return <Comp>{{}}</Comp>; // Runtime Error: Objects not valid as React Child!
}

This is because ReactNode includes ReactFragment which allows a {} type, which is too wide. Fixing this would break a lot of libraries, so for now you just have to be mindful that ReactNode is not absolutely bulletproof.

Thanks @pomle for raising this.

JSX.Element vs React.ReactNode?

Quote @ferdaber: A more technical explanation is that a valid React node is not the same thing as what is returned by React.createElement. Regardless of what a component ends up rendering, React.createElement always returns an object, which is the JSX.Element interface, but React.ReactNode is the set of all possible return values of a component.

  • JSX.Element -> Return value of React.createElement
  • React.ReactNode -> Return value of a component

More discussion: Where ReactNode does not overlap with JSX.Element

Something to add? File an issue.

Types or Interfaces?

You can use either Types or Interfaces to type Props and State, so naturally the question arises - which do you use?

TL;DR

Use Interface until You Need Type - orta.

More Advice

Here's a helpful rule of thumb:

  • always use interface for public API's definition when authoring a library or 3rd party ambient type definitions, as this allows a consumer to extend them via declaration merging if some definitions are missing.

  • consider using type for your React Component Props and State, for consistency and because it is more constrained.

You can read more about the reasoning behind this rule of thumb in Interface vs Type alias in TypeScript 2.7.

The TypeScript Handbook now also includes guidance on Differences Between Type Aliases and Interfaces.

Note: At scale, there are performance reasons to prefer interfaces (see official Microsoft notes on this) but take this with a grain of salt

Types are useful for union types (e.g. type MyType = TypeA | TypeB) whereas Interfaces are better for declaring dictionary shapes and then implementing or extending them.

Useful table for Types vs Interfaces

It's a nuanced topic, don't get too hung up on it. Here's a handy table:

AspectTypeInterface
Can describe functionsโœ…โœ…
Can describe constructorsโœ…โœ…
Can describe tuplesโœ…โœ…
Interfaces can extend itโš ๏ธโœ…
Classes can extend it๐Ÿšซโœ…
Classes can implement it (implements)โš ๏ธโœ…
Can intersect another one of its kindโœ…โš ๏ธ
Can create a union with another one of its kindโœ…๐Ÿšซ
Can be used to create mapped typesโœ…๐Ÿšซ
Can be mapped over with mapped typesโœ…โœ…
Expands in error messages and logsโœ…๐Ÿšซ
Can be augmented๐Ÿšซโœ…
Can be recursiveโš ๏ธโœ…

โš ๏ธ In some cases

(source: Karol Majewski)

Something to add? File an issue.