"Welcome back to Jurassic Park," says Dr. Ellie Sattler, paleo-botanist. "You've already learned TypeScript as a standalone language. But in the real world, we rarely use pure TypeScript — we usually integrate it with frameworks that help build complex applications. Come, I'll show you how TypeScript works with the most popular tools."
React is one of the most popular libraries for building user interfaces. TypeScript integrates wonderfully with it, providing strong typing for components, props, and hooks.
1// Interface for component props
2interface DinoCardProps {
3 name: string;
4 species: string;
5 isDangerous: boolean;
6 age?: number; // Optional field
7 onEscape?: () => void; // Optional callback function
8}
9
10// Functional component with typed props
11const DinoCard: React.FC<DinoCardProps> = ({
12 name,
13 species,
14 isDangerous,
15 age,
16 onEscape
17}) => {
18 return (
19 <div className="dino-card">
20 <h2>{name}</h2>
21 <p>Species: {species}</p>
22 <p>Age: {age || 'Unknown'}</p>
23 <div className={isDangerous ? 'danger' : 'safe'}>
24 {isDangerous ? 'DANGEROUS' : 'Safe'}
25 </div>
26 {onEscape && (
27 <button onClick={onEscape}>
28 Report escape
29 </button>
30 )}
31 </div>
32 );
33};
34
35// Usage with full typing
36const App = () => {
37 const handleEscape = () => {
38 console.log('ALARM: Dinosaur escaped!');
39 };
40
41 return (
42 <DinoCard
43 name="Blue"
44 species="Velociraptor"
45 isDangerous={true}
46 age={4}
47 onEscape={handleEscape}
48 />
49 );
50};1import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
2
3interface Dinosaur {
4 id: number;
5 name: string;
6 species: string;
7 status: 'contained' | 'escaped' | 'feeding';
8}
9
10const DinosaurMonitor: React.FC = () => {
11 // useState with type
12 const [dinosaurs, setDinosaurs] = useState<Dinosaur[]>([]);
13 const [loading, setLoading] = useState<boolean>(false);
14 const [error, setError] = useState<Error | null>(null);
15
16 // useRef with DOM element type
17 const alertSound = useRef<HTMLAudioElement>(null);
18
19 // useEffect - fetching data
20 useEffect(() => {
21 const fetchDinosaurs = async () => {
22 setLoading(true);
23 try {
24 const response = await fetch('/api/dinosaurs');
25 const data: Dinosaur[] = await response.json();
26 setDinosaurs(data);
27 } catch (err) {
28 setError(err as Error);
29 } finally {
30 setLoading(false);
31 }
32 };
33
34 fetchDinosaurs();
35 }, []);
36
37 // useCallback with typed parameters
38 const handleDinoEscape = useCallback((dinoId: number) => {
39 setDinosaurs(prev =>
40 prev.map(dino =>
41 dino.id === dinoId
42 ? { ...dino, status: 'escaped' as const }
43 : dino
44 )
45 );
46
47 // Play alarm
48 if (alertSound.current) {
49 alertSound.current.play();
50 }
51 }, []);
52
53 // useMemo with return type
54 const dangerousDinos = useMemo<Dinosaur[]>(() => {
55 return dinosaurs.filter(dino => dino.status === 'escaped');
56 }, [dinosaurs]);
57
58 if (loading) return <div>Loading monitoring data...</div>;
59 if (error) return <div>Error: {error.message}</div>;
60
61 return (
62 <div>
63 <h1>Dinosaur Monitor</h1>
64 <audio ref={alertSound} src="/sounds/alarm.mp3" />
65
66 {dangerousDinos.length > 0 && (
67 <div className="alert">
68 WARNING: {dangerousDinos.length} dinosaurs at large!
69 </div>
70 )}
71
72 <div className="dino-grid">
73 {dinosaurs.map(dino => (
74 <DinoCard
75 key={dino.id}
76 name={dino.name}
77 species={dino.species}
78 isDangerous={dino.status === 'escaped'}
79 onEscape={() => handleDinoEscape(dino.id)}
80 />
81 ))}
82 </div>
83 </div>
84 );
85};1// Custom hook for managing a list of dinosaurs
2interface UseDinosaursResult {
3 dinosaurs: Dinosaur[];
4 loading: boolean;
5 error: Error | null;
6 addDinosaur: (dino: Omit<Dinosaur, 'id'>) => Promise<void>;
7 removeDinosaur: (id: number) => Promise<void>;
8 updateStatus: (id: number, status: Dinosaur['status']) => void;
9}
10
11function useDinosaurs(): UseDinosaursResult {
12 const [dinosaurs, setDinosaurs] = useState<Dinosaur[]>([]);
13 const [loading, setLoading] = useState<boolean>(false);
14 const [error, setError] = useState<Error | null>(null);
15
16 const addDinosaur = useCallback(async (dino: Omit<Dinosaur, 'id'>) => {
17 setLoading(true);
18 try {
19 const response = await fetch('/api/dinosaurs', {
20 method: 'POST',
21 headers: { 'Content-Type': 'application/json' },
22 body: JSON.stringify(dino)
23 });
24 const newDino: Dinosaur = await response.json();
25 setDinosaurs(prev => [...prev, newDino]);
26 } catch (err) {
27 setError(err as Error);
28 } finally {
29 setLoading(false);
30 }
31 }, []);
32
33 const removeDinosaur = useCallback(async (id: number) => {
34 setLoading(true);
35 try {
36 await fetch(`/api/dinosaurs/${id}`, { method: 'DELETE' });
37 setDinosaurs(prev => prev.filter(d => d.id !== id));
38 } catch (err) {
39 setError(err as Error);
40 } finally {
41 setLoading(false);
42 }
43 }, []);
44
45 const updateStatus = useCallback((id: number, status: Dinosaur['status']) => {
46 setDinosaurs(prev =>
47 prev.map(dino =>
48 dino.id === id ? { ...dino, status } : dino
49 )
50 );
51 }, []);
52
53 return {
54 dinosaurs,
55 loading,
56 error,
57 addDinosaur,
58 removeDinosaur,
59 updateStatus
60 };
61}
62
63// Using the custom hook
64const ParkManager: React.FC = () => {
65 const {
66 dinosaurs,
67 loading,
68 error,
69 addDinosaur,
70 updateStatus
71 } = useDinosaurs();
72
73 const handleFeedDino = (id: number) => {
74 updateStatus(id, 'feeding');
75 setTimeout(() => updateStatus(id, 'contained'), 5000);
76 };
77
78 return (
79 <div>
80 {/* Component using the hook */}
81 </div>
82 );
83};1import express, { Request, Response, NextFunction } from 'express';
2
3// Interfaces for data models
4interface Dinosaur {
5 id: number;
6 name: string;
7 species: string;
8 paddock: number;
9 feedingSchedule: string[];
10}
11
12interface CreateDinosaurDTO {
13 name: string;
14 species: string;
15 paddock: number;
16}
17
18// Extending Request with custom fields
19interface AuthRequest extends Request {
20 user?: {
21 id: number;
22 role: 'admin' | 'keeper' | 'visitor';
23 };
24}
25
26// Controller with typing
27class DinosaurController {
28 // GET /api/dinosaurs
29 async getAll(req: Request, res: Response<Dinosaur[]>, next: NextFunction) {
30 try {
31 const dinosaurs = await this.dinosaurService.findAll();
32 res.json(dinosaurs);
33 } catch (error) {
34 next(error);
35 }
36 }
37
38 // GET /api/dinosaurs/:id
39 async getById(
40 req: Request<{ id: string }>,
41 res: Response<Dinosaur>,
42 next: NextFunction
43 ) {
44 try {
45 const id = parseInt(req.params.id);
46 const dinosaur = await this.dinosaurService.findById(id);
47
48 if (!dinosaur) {
49 res.status(404).json({ error: 'Dinosaur not found' } as any);
50 return;
51 }
52
53 res.json(dinosaur);
54 } catch (error) {
55 next(error);
56 }
57 }
58
59 // POST /api/dinosaurs
60 async create(
61 req: Request<{}, {}, CreateDinosaurDTO>,
62 res: Response<Dinosaur>,
63 next: NextFunction
64 ) {
65 try {
66 const newDino = await this.dinosaurService.create(req.body);
67 res.status(201).json(newDino);
68 } catch (error) {
69 next(error);
70 }
71 }
72}
73
74// Middleware with typing
75const authenticateUser = (
76 req: AuthRequest,
77 res: Response,
78 next: NextFunction
79): void => {
80 const token = req.headers.authorization?.split(' ')[1];
81
82 if (!token) {
83 res.status(401).json({ error: 'No token provided' });
84 return;
85 }
86
87 try {
88 const decoded = verifyToken(token);
89 req.user = decoded;
90 next();
91 } catch (error) {
92 res.status(401).json({ error: 'Invalid token' });
93 }
94};
95
96// Routing
97const app = express();
98
99app.use(express.json());
100
101const dinoController = new DinosaurController();
102
103app.get('/api/dinosaurs', dinoController.getAll.bind(dinoController));
104app.get('/api/dinosaurs/:id', dinoController.getById.bind(dinoController));
105app.post('/api/dinosaurs', authenticateUser, dinoController.create.bind(dinoController));1// Repository Pattern with TypeScript
2interface Repository<T> {
3 findAll(): Promise<T[]>;
4 findById(id: number): Promise<T | null>;
5 create(data: Omit<T, 'id'>): Promise<T>;
6 update(id: number, data: Partial<T>): Promise<T>;
7 delete(id: number): Promise<void>;
8}
9
10class DinosaurRepository implements Repository<Dinosaur> {
11 async findAll(): Promise<Dinosaur[]> {
12 // Database implementation
13 return [];
14 }
15
16 async findById(id: number): Promise<Dinosaur | null> {
17 // Implementation
18 return null;
19 }
20
21 async create(data: Omit<Dinosaur, 'id'>): Promise<Dinosaur> {
22 // Implementation
23 return { id: 1, ...data } as Dinosaur;
24 }
25
26 async update(id: number, data: Partial<Dinosaur>): Promise<Dinosaur> {
27 // Implementation
28 return {} as Dinosaur;
29 }
30
31 async delete(id: number): Promise<void> {
32 // Implementation
33 }
34}
35
36// Service Layer
37class DinosaurService {
38 constructor(private repository: DinosaurRepository) {}
39
40 async getAllDinosaurs(): Promise<Dinosaur[]> {
41 return this.repository.findAll();
42 }
43
44 async getDinosaurById(id: number): Promise<Dinosaur> {
45 const dino = await this.repository.findById(id);
46 if (!dino) {
47 throw new Error(`Dinosaur with id ${id} not found`);
48 }
49 return dino;
50 }
51
52 async createDinosaur(data: CreateDinosaurDTO): Promise<Dinosaur> {
53 // Validation
54 this.validateDinosaurData(data);
55
56 // Creation
57 return this.repository.create({
58 ...data,
59 feedingSchedule: this.generateFeedingSchedule(data.species)
60 });
61 }
62
63 private validateDinosaurData(data: CreateDinosaurDTO): void {
64 if (!data.name || data.name.length < 2) {
65 throw new Error('Invalid dinosaur name');
66 }
67 if (data.paddock < 1 || data.paddock > 10) {
68 throw new Error('Invalid paddock number');
69 }
70 }
71
72 private generateFeedingSchedule(species: string): string[] {
73 // Schedule generation logic
74 return ['08:00', '14:00', '20:00'];
75 }
76}Next.js has built-in TypeScript support — just create a
.ts or .tsx file and Next.js will automatically configure TypeScript!1// pages/dinosaurs/[id].tsx - Dynamic route with typing
2import { GetServerSideProps, NextPage } from 'next';
3
4interface DinosaurPageProps {
5 dinosaur: Dinosaur;
6 lastUpdated: string;
7}
8
9// Page component
10const DinosaurPage: NextPage<DinosaurPageProps> = ({ dinosaur, lastUpdated }) => {
11 return (
12 <div>
13 <h1>{dinosaur.name}</h1>
14 <p>Species: {dinosaur.species}</p>
15 <p>Paddock: {dinosaur.paddock}</p>
16 <small>Last updated: {lastUpdated}</small>
17 </div>
18 );
19};
20
21// Server-side rendering with typing
22export const getServerSideProps: GetServerSideProps<DinosaurPageProps> = async (context) => {
23 const { id } = context.params as { id: string };
24
25 try {
26 const response = await fetch(`https://api.park.com/dinosaurs/${id}`);
27 const dinosaur: Dinosaur = await response.json();
28
29 return {
30 props: {
31 dinosaur,
32 lastUpdated: new Date().toISOString()
33 }
34 };
35 } catch (error) {
36 return {
37 notFound: true
38 };
39 }
40};
41
42export default DinosaurPage;1// pages/api/dinosaurs/[id].ts
2import type { NextApiRequest, NextApiResponse } from 'next';
3
4type ResponseData = {
5 dinosaur?: Dinosaur;
6 error?: string;
7};
8
9export default async function handler(
10 req: NextApiRequest,
11 res: NextApiResponse<ResponseData>
12) {
13 const { id } = req.query;
14
15 if (req.method === 'GET') {
16 try {
17 const dinosaur = await getDinosaurById(Number(id));
18 res.status(200).json({ dinosaur });
19 } catch (error) {
20 res.status(404).json({ error: 'Dinosaur not found' });
21 }
22 } else if (req.method === 'PUT') {
23 try {
24 const updatedDino = await updateDinosaur(Number(id), req.body);
25 res.status(200).json({ dinosaur: updatedDino });
26 } catch (error) {
27 res.status(400).json({ error: 'Update failed' });
28 }
29 } else {
30 res.status(405).json({ error: 'Method not allowed' });
31 }
32}1// app/dinosaurs/[id]/page.tsx
2interface PageProps {
3 params: { id: string };
4 searchParams: { [key: string]: string | string[] | undefined };
5}
6
7// Server Component
8export default async function DinosaurDetailPage({ params }: PageProps) {
9 const dinosaur = await getDinosaurById(params.id);
10
11 return (
12 <div>
13 <h1>{dinosaur.name}</h1>
14 <DinosaurDetails dino={dinosaur} />
15 </div>
16 );
17}
18
19// Client Component
20'use client';
21
22interface DinosaurDetailsProps {
23 dino: Dinosaur;
24}
25
26export function DinosaurDetails({ dino }: DinosaurDetailsProps) {
27 const [isFavorite, setIsFavorite] = useState(false);
28
29 return (
30 <div>
31 <button onClick={() => setIsFavorite(!isFavorite)}>
32 {isFavorite ? 'Favorited' : 'Add to favorites'}
33 </button>
34 {/* Rest of the component */}
35 </div>
36 );
37}1// Universal list component with typing
2interface ListProps<T> {
3 items: T[];
4 renderItem: (item: T, index: number) => React.ReactNode;
5 keyExtractor: (item: T) => string | number;
6 emptyMessage?: string;
7}
8
9function List<T>({ items, renderItem, keyExtractor, emptyMessage }: ListProps<T>) {
10 if (items.length === 0) {
11 return <div>{emptyMessage || 'No items'}</div>;
12 }
13
14 return (
15 <ul>
16 {items.map((item, index) => (
17 <li key={keyExtractor(item)}>
18 {renderItem(item, index)}
19 </li>
20 ))}
21 </ul>
22 );
23}
24
25// Usage
26const DinosaurList = () => {
27 const dinosaurs: Dinosaur[] = [
28 { id: 1, name: 'Rex', species: 'T-Rex', paddock: 1, feedingSchedule: [] },
29 { id: 2, name: 'Blue', species: 'Velociraptor', paddock: 3, feedingSchedule: [] }
30 ];
31
32 return (
33 <List<Dinosaur>
34 items={dinosaurs}
35 keyExtractor={(dino) => dino.id}
36 renderItem={(dino) => (
37 <div>
38 <strong>{dino.name}</strong> - {dino.species}
39 </div>
40 )}
41 emptyMessage="No dinosaurs in the system"
42 />
43 );
44};1import { useForm, SubmitHandler } from 'react-hook-form';
2
3interface DinosaurFormData {
4 name: string;
5 species: string;
6 paddock: number;
7 isDangerous: boolean;
8 feedingTimes: string[];
9}
10
11const DinosaurForm: React.FC = () => {
12 const {
13 register,
14 handleSubmit,
15 formState: { errors }
16 } = useForm<DinosaurFormData>();
17
18 const onSubmit: SubmitHandler<DinosaurFormData> = async (data) => {
19 try {
20 const response = await fetch('/api/dinosaurs', {
21 method: 'POST',
22 headers: { 'Content-Type': 'application/json' },
23 body: JSON.stringify(data)
24 });
25
26 if (response.ok) {
27 alert('Dinosaur added!');
28 }
29 } catch (error) {
30 console.error('Error adding dinosaur:', error);
31 }
32 };
33
34 return (
35 <form onSubmit={handleSubmit(onSubmit)}>
36 <input
37 {...register('name', { required: 'Name is required' })}
38 placeholder="Dinosaur name"
39 />
40 {errors.name && <span>{errors.name.message}</span>}
41
42 <input
43 {...register('species', { required: true })}
44 placeholder="Species"
45 />
46
47 <input
48 type="number"
49 {...register('paddock', {
50 required: true,
51 min: 1,
52 max: 10
53 })}
54 placeholder="Paddock number"
55 />
56
57 <label>
58 <input type="checkbox" {...register('isDangerous')} />
59 Dangerous species
60 </label>
61
62 <button type="submit">Add dinosaur</button>
63 </form>
64 );
65};"You see?" Dr. Sattler smiles. "TypeScript integrates beautifully with all popular frameworks:"
React.FC<Props>useState<T>, useRef<HTMLElement>)getServerSideProps, getStaticPropsKey benefits:
"TypeScript," Dr. Sattler concludes, "is like adding security systems to our Jurassic Park infrastructure. It requires a bit more work upfront, but in the long run, it saves us tons of problems!"