TypeScript React Components — Complete Guide
In this tutorial, you will learn about TypeScript React Components. We cover key concepts, practical examples, and best practices to help you master this topic.
Typing React components with TypeScript gives you compile-time prop validation, autocompletion in JSX, and self-documenting component interfaces — eliminating an entire category of runtime "undefined is not an object" errors.
What You'll Learn
- Props typing with interfaces
- FC (FunctionComponent) type
- Children prop patterns
- Default props and optional props
- Generic components
Why It Matters
Without TypeScript, React prop types are checked at runtime (PropTypes) or not at all. TypeScript catches prop mismatches, missing required props, and wrong prop types during development, before they reach users.
Real-World Use
DodaTech's Durga Antivirus Pro dashboard has 200+ typed components. A ScanResultCard component with properly typed props ensures every usage passes threats: string[], status: "clean" | "infected", and timestamp: Date. Wrong prop types are caught at compile time, not when a customer reports a broken dashboard.
Learning Path
flowchart LR A[Bundling] --> B[React Components] B --> C[React Hooks] B --> D[You Are Here] C --> E[React Events] E --> F[React Context]
Basic Props Interface
import { ReactNode } from 'react';
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
onClick: () => void;
children?: ReactNode;
}
function Button({ label, variant = 'primary', disabled = false, onClick, children }: ButtonProps) {
const baseClass = 'btn';
const classes = `${baseClass} ${baseClass}--${variant}`;
return (
<button className={classes} disabled={disabled} onClick={onClick}>
{children || label}
</button>
);
}
// Usage
<Button label="Submit" onClick={() => console.log('clicked')}>
Submit Form
</Button>
The FC Type (Legacy)
Before React 18, the React.FC type was common:
import { FC, ReactNode } from 'react';
interface CardProps {
title: string;
children: ReactNode;
}
const Card: FC<CardProps> = ({ title, children }) => {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
};
Note: FC implicitly includes children and displayName. Many teams now prefer explicit props without FC.
Children Patterns
interface LayoutProps {
children: ReactNode; // Any renderable content
header?: ReactNode; // Optional header
footer?: ReactElement; // Only a single React element
}
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map((item, i) => <li key={i}>{renderItem(item, i)}</li>)}</ul>;
}
// Usage
<List items={['Alice', 'Bob']} renderItem={(name) => <span>{name}</span>} />
Default Props
interface AlertProps {
message: string;
type?: 'info' | 'warning' | 'error';
dismissible?: boolean;
onDismiss?: () => void;
}
function Alert({ message, type = 'info', dismissible = false, onDismiss }: AlertProps) {
return (
<div className={`alert alert--${type}`}>
<span>{message}</span>
{dismissible && <button onClick={onDismiss}>X</button>}
</div>
);
}
Default values at destructuring provide type-safe defaults without runtime overhead.
Generic Components
interface TableProps<T extends Record<string, unknown>> {
data: T[];
columns: { key: keyof T; header: string }[];
onRowClick?: (row: T) => void;
}
function Table<T extends Record<string, unknown>>({ data, columns, onRowClick }: TableProps<T>) {
return (
<table>
<thead>
<tr>{columns.map(col => <th key={String(col.key)}>{col.header}</th>)}</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i} onClick={() => onRowClick?.(row)}>
{columns.map(col => <td key={String(col.key)}>{String(row[col.key])}</td>)}
</tr>
))}
</tbody>
</table>
);
}
interface User { id: number; name: string; email: string; }
const users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
<Table data={users} columns={[
{ key: 'id', header: 'ID' },
{ key: 'name', header: 'Name' },
{ key: 'email', header: 'Email' },
]} />
Common Mistakes
1. Not Typing Event Handlers
// Bad — implicit any
function handleClick(e) { }
// Good — typed
function handleClick(e: React.MouseEvent<HTMLButtonElement>) { }
2. Using any for Props
Always define an interface or type for component props.
3. Forgetting Children Type
children can be ReactNode (most flexible) or ReactElement (strict).
4. Not Using Generic Components for Reusable Data Displays
Tables, lists, and selectors should accept generic props.
5. Spreading Props Without Type Safety
// Bad — loses type checking
function BadButton(props: any) { return <button {...props} />; }
// Good — preserves type checking
function GoodButton(props: ButtonProps) { return <button {...props} />; }
Practice Questions
What is
ReactNode? A type representing any renderable content: JSX, strings, numbers, fragments, portals, or null.When would you use a generic component? When the component's data type varies, like a Table or List component.
What does
React.FCprovide? Implicitly addschildrenanddisplayNameto the props type. Commonly avoided now for explicitness.How do you make a prop optional? Add
?to the interface property:variant?: 'primary' | 'secondary'.
Challenge: Create a generic Select component that accepts an array of options with typed values, a selected value, and an onChange callback. Use proper generics so the onChange receives the correct value type.
FAQ
Mini Project: Typed Component Library
Create three typed components: Button, Modal, and DataTable.
// Button.tsx
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
onClick: () => void;
children: React.ReactNode;
}
export function Button({ variant = 'primary', size = 'md', disabled, loading, onClick, children }: ButtonProps) {
return (
<button
className={`btn btn--${variant} btn--${size}`}
disabled={disabled || loading}
onClick={onClick}
>
{loading ? 'Loading...' : children}
</button>
);
}
What's Next
Now explore typed React hooks:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/36-bundling" >}} | Review Bundling |
| {{< ref "/programming-languages/typescript/38-react-hooks" >}} | useState, useEffect, useRef with TS |
| {{< ref "/programming-languages/typescript/39-react-events" >}} | Event typing in React |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro