id | title |
---|---|
basic_type_example |
Typing Component Props |
This is intended as a basic orientation and reference for React developers familiarizing with TypeScript.
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.
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
children: React.ReactNode; // best, accepts everything
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
}
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 ofReact.createElement
React.ReactNode
-> Return value of a component
More discussion: Where ReactNode does not overlap with JSX.Element
Something to add? File an issue.
You can use either Types or Interfaces to type Props and State, so naturally the question arises - which do you use?
interface
s are different from type
s in TypeScript, but they can be used for very similar things as far as common React uses cases are concerned. 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.
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:Aspect | Type | Interface |
---|---|---|
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 | ✅ |
(source: Karol Majewski)