Home/TypeScript with React
TypeScript with React: Complete Guide
Typing React components correctly eliminates an entire class of prop bugs and makes your IDE autocomplete genuinely useful. This guide covers every pattern you will encounter — from simple props to generic components.
Typing Functional Components
The simplest approach is to define a props interface and pass it directly to the function parameter. Avoid React.FC — it is no longer recommended and was removed from many starter templates.
import React from "react";
// Define props as an interface
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean; // optional
variant?: "primary" | "secondary" | "ghost";
}
// Type the function parameter directly — no React.FC needed
function Button({ label, onClick, disabled = false, variant = "primary" }: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{label}
</button>
);
}
// Usage — TypeScript errors if label or onClick is missing
<Button label="Save" onClick={() => console.log("saved")} />
<Button label="Cancel" onClick={handleCancel} variant="ghost" />Props: interface vs type
Both interface and type work for props. Use type when you need union types or computed props.
// interface — extendable, conventional for object shapes
interface CardProps {
title: string;
description: string;
imageUrl?: string;
}
// Extend for a special variant
interface FeatureCardProps extends CardProps {
badge: string;
badgeColor: "green" | "blue" | "red";
}
// type — required when props include union or conditional logic
type AlertProps =
| { type: "success"; message: string }
| { type: "error"; message: string; code: number }
| { type: "info"; message: string; dismissible?: boolean };
function Alert(props: AlertProps) {
if (props.type === "error") {
// TypeScript knows 'code' is available here
return <div>Error {props.code}: {props.message}</div>;
}
return <div>{props.message}</div>;
}useState with TypeScript
TypeScript infers state type from the initial value. When the initial value is null or undefined, you must provide the generic argument.
import { useState } from "react";
interface User {
id: number;
name: string;
email: string;
}
function UserProfile() {
// Inferred from initial value — no annotation needed
const [count, setCount] = useState(0); // number
const [name, setName] = useState(""); // string
const [active, setActive] = useState(false); // boolean
// Must specify type when initial value is null
const [user, setUser] = useState<User | null>(null);
// Array of objects — infer from initial value or specify explicitly
const [users, setUsers] = useState<User[]>([]);
// Object state
const [form, setForm] = useState({ email: "", password: "" });
// setForm({ email: "a@b.com", password: "secret" }); ✓
// setForm({ email: "a@b.com" }); ✗ missing password
return <div>{user?.name}</div>;
}useRef with TypeScript
useRef has two distinct uses: holding a DOM node and holding a mutable value. They are typed differently.
import { useRef, useEffect } from "react";
function SearchInput() {
// DOM ref — initialize with null, TypeScript enforces null check
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
// Must check .current before accessing — it starts as null
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="search" />;
}
function Timer() {
// Mutable ref — no null, holds a timer ID
const timerId = useRef<ReturnType<typeof setInterval>>(null!);
function start() {
timerId.current = setInterval(() => console.log("tick"), 1000);
}
function stop() {
clearInterval(timerId.current);
}
return (
<div>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}Typing Event Handlers
React ships with typed event interfaces for every DOM event. The element type is the generic argument.
import React from "react";
function Form() {
// ChangeEvent<HTMLInputElement> for text inputs
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value); // string — fully typed
};
// ChangeEvent<HTMLSelectElement> for dropdowns
const handleSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
console.log(e.target.value);
};
// FormEvent<HTMLFormElement> for form submission
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// process form data
};
// MouseEvent<HTMLButtonElement> for button clicks
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.clientX, e.clientY); // mouse position
};
// KeyboardEvent<HTMLInputElement> for key presses
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") console.log("submitted");
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} onKeyDown={handleKeyDown} />
<select onChange={handleSelect}><option>A</option></select>
<button type="button" onClick={handleClick}>Click me</button>
</form>
);
}Typing Children
import React from "react";
// ReactNode accepts any valid JSX child
interface WrapperProps {
children: React.ReactNode;
className?: string;
}
function Card({ children, className = "" }: WrapperProps) {
return <div className={`card ${className}`}>{children}</div>;
}
// ReactElement accepts only JSX elements (not strings/numbers)
interface IconButtonProps {
icon: React.ReactElement;
label: string;
onClick: () => void;
}
function IconButton({ icon, label, onClick }: IconButtonProps) {
return (
<button onClick={onClick} aria-label={label}>
{icon}
<span>{label}</span>
</button>
);
}
// PropsWithChildren utility — adds children automatically
import { PropsWithChildren } from "react";
function Section({ children, title }: PropsWithChildren<{ title: string }>) {
return (
<section>
<h2>{title}</h2>
{children}
</section>
);
}Generic React Components
Components can be generic just like functions. This is especially useful for list, table, and select components that work with any data type.
// A type-safe list component — works with any item type
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string | number;
emptyMessage?: string;
}
function List<T>({ items, renderItem, keyExtractor, emptyMessage = "No items" }: ListProps<T>) {
if (items.length === 0) return <p>{emptyMessage}</p>;
return (
<ul>
{items.map((item, i) => (
<li key={keyExtractor(item)}>{renderItem(item, i)}</li>
))}
</ul>
);
}
// Usage — T is inferred from 'items'
<List
items={users}
keyExtractor={(u) => u.id}
renderItem={(u) => <span>{u.name}</span>}
/>Frequently Asked Questions
Should I use interface or type for React component props?
Both work. The React community conventionally uses 'interface' because it is extendable and produces clearer error messages. Use 'type' when you need unions, intersections, or computed types in your props.
How do I type useState in TypeScript?
TypeScript usually infers the state type from the initial value. When you initialize with null (common for async data), provide the type explicitly: useState<User | null>(null). The setter will also be typed correctly.
How do I type a React event handler in TypeScript?
Use React.ChangeEvent<HTMLInputElement> for input onChange, React.MouseEvent<HTMLButtonElement> for button onClick, React.FormEvent<HTMLFormElement> for form onSubmit. These give full autocomplete on the event object.
What is React.FC and should I use it?
React.FC is no longer recommended. It added implicit children to props (removed in React 18's types) and does not add real value. The modern approach: define props with an interface and type the function directly as function MyComponent(props: MyProps) {}.
How do I type useRef in TypeScript?
For DOM refs, initialize with null: useRef<HTMLInputElement>(null). For mutable values like a timer ID: useRef<number>(0). TypeScript enforces null checks for DOM refs before accessing .current.
How do I type the children prop in TypeScript React?
Use React.ReactNode as the children type. It accepts strings, numbers, JSX elements, arrays, fragments, and null/undefined. If you only want JSX elements, use React.ReactElement instead.
Convert your React JS to React TSX
Paste a JavaScript React component and get back properly typed TypeScript — props interfaces, hook types, and event handlers included.
Convert now →From the blog
View all →
May 13, 2026
Convert XML to JSON Online: Complete Guide for Developers (2026)

May 11, 2026
Convert CSV to JSON Online Free (Best Developer Guide 2026)

May 6, 2026
Convert YAML to JSON Online Free (2026 Developer Guide)

Apr 30, 2026
Convert JSON to Zod Schema Online (2026) – Free Tool & Complete Guide

Apr 30, 2026
Top 50 JavaScript to TypeScript Converters (2026) – Free, AI & Online Tools