Why TypeScript in React
In professional projects, React is almost always written with TypeScript. The
benefit is huge: the editor warns you about errors before running (a misspelled
prop, a value that can be undefined, a handler with the wrong type), and the code
documents itself. For React, the most important things are typing the props, the
state, and the events.
Note: TypeScript compiles to JavaScript; in the browser it always ends up running JS. Here we'll see the theory with
.tsxexamples; the practical exercises in this module are written in valid JS.
Typing props with interface or type
A component's props are an object, so we describe their shape with an
interface (or a type). Then we use it to type the parameter:
interface GreetingProps {
name: string;
age?: number; // the "?" makes it optional
}
function Greeting({ name, age }: GreetingProps) {
return <h1>Hi, {name}{age ? ` (${age})` : ""}</h1>;
}
name: string→ required. If you forget it when using<Greeting />, error at compile time.age?: number→ optional. Inside the component,ageisnumber | undefined.
interface and type are almost interchangeable for props; use whichever your
team prefers. type also allows unions (type State = "ok" | "error").
Components: typed function or React.FC
The recommended approach today is a plain function with typed props (like
above). There's also React.FC, which types the whole component and includes
children implicitly in old versions:
const Button: React.FC<{ text: string }> = ({ text }) => {
return <button>{text}</button>;
};
In modern React, the typed function is preferred over
React.FC, because it's more explicit (you declarechildrenonly if you need it) and more flexible.
Typed state: useState<T>
useState infers the type from the initial value:
const [name, setName] = useState(""); // string
const [active, setActive] = useState(false); // boolean
When the initial value isn't enough to infer (e.g. it starts as null but will
later store an object), you annotate it explicitly with useState<T>:
interface User { id: number; name: string; }
const [user, setUser] = useState<User | null>(null);
Typing events
Event handlers receive a React event, not a native DOM one. The
types live in the React.* namespace:
function Field() {
const [text, setText] = useState("");
function onType(e: React.ChangeEvent<HTMLInputElement>) {
setText(e.target.value);
}
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
}
return (
<form onSubmit={onSubmit}>
<input value={text} onChange={onType} />
</form>
);
}
The most common ones: React.ChangeEvent<HTMLInputElement> (inputs),
React.MouseEvent<HTMLButtonElement> (clicks), React.FormEvent (forms), and
React.KeyboardEvent (keyboard). If you write the handler inline
(onClick={(e) => ...}), TypeScript usually infers the event type for you.
children and React.ReactNode
children is "whatever goes between the opening and closing tags" of the component.
Since it can be text, a number, a JSX element, a list of them, or nothing, its
type is React.ReactNode: the most general type for "anything React
can render".
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps) {
return (
<section>
<h2>{title}</h2>
<div>{children}</div>
</section>
);
}
// Usage:
<Card title="Hi">
<p>Any content goes here.</p>
</Card>
Mental rule: use
React.ReactNodeforchildrenand for any prop that receives "renderable content". For a prop that is one specific element, there'sReact.ReactElement.