React
React with TypeScript
Add type safety to your React components and catch errors before they reach production
TypeScript transforms React development. No more wondering what props a component expects. No more silent runtime failures from typos. Type safety catches issues at build time. The DataFlow team discovered this the hard way. Their dashboard worked perfectly in testing, but crashed in production when the API returned unexpected data shapes. One missing property brought down the entire analytics view.Why TypeScript with React
JavaScript is dynamically typed. Variables can be anything at any time. React components receive props that could be strings, numbers, objects, or undefined. TypeScript adds a type layer that prevents these surprises. Think of TypeScript as a spell-checker for your code. Just as spell-check catches typos before you send an email, TypeScript catches type errors before your app runs.Benefits in React
Props get explicit types, state updates are validated, and your IDE provides accurate autocompletion. Refactoring becomes safe because TypeScript tracks every component that uses changed props.
Setting Up React with TypeScript
Modern React projects support TypeScript out of the box. Create React App, Vite, and Next.js all include TypeScript templates.// Create new TypeScript React project
npx create-react-app dataflow-ts --template typescript
// Or with Vite (faster)
npm create vite@latest dataflow-ts -- --template react-ts
// Install types for existing project
npm install typescript @types/react @types/react-dom
@types/react package provides TypeScript definitions for React. It tells TypeScript what useState, useEffect, and component props should look like.
Typing React Components
React components are just functions that return JSX. TypeScript can infer the return type, but you should explicitly type the props.// Basic typed component for DataFlow stats
interface StatsCardProps {
title: string;
value: number;
trend: 'up' | 'down' | 'stable';
}
function StatsCard({ title, value, trend }: StatsCardProps) {
return (
<div className="stats-card">
<h3>{title}</h3>
<p>${value.toLocaleString()}</p>
<span className={`trend-${trend}`}>
{trend === 'up' ? '↗' : trend === 'down' ? '↘' : '→'}
</span>
</div>
);
}
What just happened?
The interface defines exact prop types. trend: 'up' | 'down' | 'stable' creates a union type - only those three strings are allowed. Try this: pass an invalid trend value and watch TypeScript catch the error.
useState with Types
React hooks need type annotations when TypeScript can't infer the type.useState often infers correctly from initial values, but complex types require explicit annotation.
// DataFlow user management with typed state
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user' | 'viewer';
}
function UserManager() {
const [users, setUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const addUser = (userData: Omit<User, 'id'>) => {
const newUser: User = {
id: Date.now(),
...userData
};
setUsers(prev => [...prev, newUser]);
};
return (
<div>
<h2>Users: {users.length}</h2>
<button onClick={() => addUser({
name: 'John Doe',
email: 'john@dataflow.com',
role: 'user'
})}>
Add User
</button>
</div>
);
}
What just happened?
Omit<User, 'id'> creates a type with all User properties except id. useState<User[]> tells TypeScript this state holds an array of User objects. Try this: hover over variables in VS Code to see their inferred types.
Event Handling Types
React events have specific TypeScript types. Button clicks getMouseEvent, form inputs get ChangeEvent. These types provide access to event properties with full autocomplete.
// DataFlow search with typed events
import { ChangeEvent, FormEvent, useState } from 'react';
function SearchBar() {
const [query, setQuery] = useState('');
const [filters, setFilters] = useState({
category: '',
dateRange: '30days'
});
const handleSearch = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log('Searching for:', query);
};
const handleQueryChange = (e: ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
};
const handleFilterChange = (e: ChangeEvent<HTMLSelectElement>) => {
setFilters(prev => ({
...prev,
[e.target.name]: e.target.value
}));
};
return (
<form onSubmit={handleSearch}>
<input
type="text"
value={query}
onChange={handleQueryChange}
placeholder="Search transactions..."
/>
<select name="category" onChange={handleFilterChange}>
<option value="">All Categories</option>
<option value="sales">Sales</option>
<option value="marketing">Marketing</option>
</select>
<button type="submit">Search</button>
</form>
);
}
Advanced TypeScript Patterns
Real applications need sophisticated type patterns. Generic components, conditional types, and mapped types solve complex scenarios that basic interfaces cannot handle.Generic Components
Components that work with any data type while maintaining type safety
Utility Types
Pick, Omit, Partial transform existing types into new shapes
Conditional Types
Types that change based on conditions, like ternary operators for types
Strict Mode
Enable strict TypeScript options to catch more potential issues
// Generic DataFlow table component
interface TableProps<T> {
data: T[];
columns: Array<{
key: keyof T;
label: string;
render?: (value: T[keyof T], row: T) => React.ReactNode;
}>;
onRowClick?: (row: T) => void;
}
function DataTable<T>({ data, columns, onRowClick }: TableProps<T>) {
return (
<table>
<thead>
<tr>
{columns.map(col => (
<th key={String(col.key)}>{col.label}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, index) => (
<tr key={index} onClick={() => onRowClick?.(row)}>
{columns.map(col => (
<td key={String(col.key)}>
{col.render ? col.render(row[col.key], row) : String(row[col.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
What just happened?
The generic <T> makes this table work with any data type. keyof T ensures column keys exist on the data objects. Custom render functions get full type safety for their parameters.
Common TypeScript Errors
TypeScript errors can be cryptic. Understanding common patterns helps debug issues faster. Most errors relate to missing properties, wrong types, or strict null checks.Property does not exist on type
Usually means you're accessing a property that TypeScript doesn't know about. Check your interface definitions and make sure all required properties are included.
// Common fixes for TypeScript errors in DataFlow
interface ApiResponse<T> {
data: T;
success: boolean;
message?: string; // Optional property
}
// Type assertion when you know better than TypeScript
const userDiv = document.getElementById('user') as HTMLDivElement;
// Optional chaining prevents crashes
const userName = user?.profile?.displayName ?? 'Guest';
// Non-null assertion when you're certain
const apiKey = process.env.REACT_APP_API_KEY!;
// Union types for multiple possibilities
type LoadingState = 'idle' | 'loading' | 'success' | 'error';
Best Practices
Start with loose types and tighten gradually. Perfect types aren't required from day one. Focus on public APIs first - component props and function parameters that other developers use. Useany sparingly. When unavoidable, add a comment explaining why and plan to replace it. unknown is safer than any for truly dynamic content.
Type your API responses. Create interfaces matching your backend data structures. This catches API changes during development instead of production crashes.
Pro Tip: Gradual Adoption
Rename existing .js files to .tsx one at a time. Fix type errors as you go. Start with leaf components that don't have children, then work up the component tree.
Quiz
1. How do you define props for a DataFlow StatsCard component that requires a title (string) and value (number)?
2. What TypeScript type should you use for a DataFlow search input onChange handler?
3. How do you initialize useState for an array of User objects in the DataFlow user management system?
Up Next: React Accessibility
Make your React applications usable by everyone with proper ARIA attributes, keyboard navigation, and screen reader support.