React with TypeScript
TypeScript is a programming language built on top of JavaScript that adds static typing. This means types for variables, function parameters, and return values are declared, and TypeScript checks them before the code runs — catching errors at development time rather than at runtime in the browser.
Using TypeScript with React is increasingly common in professional projects. It makes components more predictable, self-documenting, and easier to maintain — especially in large codebases worked on by teams.
Setting Up a React + TypeScript Project
Create a new React project with TypeScript using Vite:
npm create vite@latest my-ts-app -- --template react-ts
cd my-ts-app
npm install
npm run dev
TypeScript files in React use the .tsx extension (instead of .jsx). Everything works the same as a regular React project, but with type annotations added.
Typing Component Props
The most immediate benefit of TypeScript in React is typing component props. This ensures that a component always receives the correct data types — and the editor will warn immediately if the wrong type is passed.
Props are typed using a type or interface definition:
// Using a type alias
type ButtonProps = {
label: string;
onClick: () => void;
disabled?: boolean; // Optional prop (the ? makes it optional)
};
function Button({ label, onClick, disabled = false }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
If someone tries to pass a number to label or forgets to pass the required onClick, TypeScript reports a type error before the code even runs.
Typing with useState
TypeScript can infer the type of state from the initial value. When the initial value is clear, no explicit type annotation is needed:
const [count, setCount] = useState(0); // TypeScript infers: number
const [name, setName] = useState(""); // TypeScript infers: string
const [isOpen, setIsOpen] = useState(false); // TypeScript infers: boolean
When the initial value is null or undefined but will later hold a specific type, the type must be declared explicitly:
// This could be null initially, but will be a string later
const [username, setUsername] = useState<string | null>(null);
// This will eventually hold a User object
const [user, setUser] = useState<User | null>(null);
Defining Data Types with Interfaces
Complex data shapes are described using interface or type:
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user" | "guest"; // Union type — only these values are allowed
}
function UserCard({ user }: { user: User }) {
return (
<div>
<h3>{user.name}</h3>
<p>Email: {user.email}</p>
<p>Role: {user.role}</p>
</div>
);
}
Typing Event Handlers
React's TypeScript types include specific types for events. The most common are:
// Click event on a button
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
console.log("Button clicked");
}
// Change event on an input
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
console.log(event.target.value);
}
// Submit event on a form
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
console.log("Form submitted");
}
Typing useRef
When using useRef with DOM elements, the element type must be specified:
import { useRef } from 'react';
function FocusInput() {
const inputRef = useRef<HTMLInputElement>(null);
function focusInput() {
inputRef.current?.focus(); // The ?. safely handles the case where current is null
}
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</div>
);
}
Typing Arrays and Lists
type Product = {
id: number;
name: string;
price: number;
};
function ProductList({ products }: { products: Product[] }) {
return (
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name} — ${product.price}
</li>
))}
</ul>
);
}
TypeScript ensures that only an array of objects matching the Product shape can be passed to this component.
Typing the children Prop
When a component accepts children, the React.ReactNode type is used:
type CardProps = {
title: string;
children: React.ReactNode;
};
function Card({ title, children }: CardProps) {
return (
<div>
<h3>{title}</h3>
{children}
</div>
);
}
Benefits of TypeScript in React
- Early error detection — Type errors appear in the editor before running the code
- Improved autocomplete — IDEs suggest the correct properties based on defined types
- Self-documenting code — Types act as inline documentation for what a component expects
- Safer refactoring — Changing a type automatically highlights everywhere the change needs to be applied
Key Points
- TypeScript adds static typing to JavaScript, catching errors at development time.
- React components written in TypeScript use the
.tsxfile extension. - Use
typeorinterfaceto define the shape of component props. - TypeScript infers types from initial values in
useState— explicit types are only needed when the type is not obvious from the initial value. - React provides specific types for events:
React.MouseEvent,React.ChangeEvent,React.FormEvent. - Union types (
"admin" | "user") restrict a prop to only specific allowed values.
The final topic covers Testing React Applications — how to write automated tests to verify that components work correctly and regressions are caught early.
