15. TypeScript with Frontend Frameworks
π Master TypeScript with React, Vue, Angular, and Svelte! Learn type-safe components, hooks, form handling, and state management. β¨
What we will learn in this post?
- π TypeScript with React - Components
- π React Hooks with TypeScript
- π TypeScript with Vue 3
- π TypeScript with Angular
- π TypeScript with Svelte
- π Form Handling and Validation
- π State Management with TypeScript
Typing React Functional Components π
React functional components are a great way to build user interfaces. When using TypeScript, we can make our components safer and easier to understand by typing them properly. Tech giants like Netflix and Airbnb have built their entire frontends with TypeScript-powered React components. Letβs explore how to do this!
Using React.FC and Explicit Return Types π₯οΈ
You can type your components using React.FC or by defining explicit return types. Hereβs how:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
interface MyComponentProps {
title: string;
children?: React.ReactNode; // Typing children prop
}
const MyComponent: React.FC<MyComponentProps> = ({ title, children }) => {
return (
<div>
<h1>{title}</h1>
{children}
</div>
);
};
Component Composition π
You can compose components easily. Just pass components as children:
1
2
3
<MyComponent title="Hello!">
<p>This is a child component.</p>
</MyComponent>
Default Props and PropTypes Deprecation β οΈ
In TypeScript, you can set default props like this:
1
2
3
MyComponent.defaultProps = {
title: 'Default Title',
};
However, propTypes are not needed with TypeScript, as TypeScript handles type checking.
Guide to React Hooks π
Understanding React Hooks
React hooks are functions that let you use state and other React features without writing a class. Professional React applications use custom hooks to share complex stateful logic across components, reducing code duplication by up to 40%. Hereβs a quick guide to some essential hooks:
1. useState</span> π οΈ
- Purpose: Manage state in functional components.
- Usage:
1
const [count, setCount] = useState<number>(0);
2. useEffect with Cleanup π
- Purpose: Perform side effects in components.
- Usage:
1 2 3 4
useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); // Cleanup }, [count]);
3. useRef</span> π
- Purpose: Access DOM elements directly.
- Usage:
1
const inputRef = useRef<HTMLInputElement>(null);
4. useCallback and useMemo π
- Purpose: Optimize performance by memoizing functions and values.
- Usage:
1 2
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); const memoizedCallback = useCallback(() => { /* function */ }, [dependency]);
5. useContext with Typed Context π
- Purpose: Access context values easily.
- Usage:
1
const value = useContext<MyContextType>(MyContext);
6. Custom Hooks π§©
- Purpose: Reuse stateful logic.
- Usage:
1 2 3
function useCustomHook() { // logic here }
Type Inference in Hooks π
Type inference helps TypeScript understand the types of your state and props automatically, making your code cleaner and safer.
Common Patterns π
- State Management: Use
useReducerfor complex state. - Effect Dependencies: Always specify dependencies in
useEffectto avoid bugs.
graph TD
A["πͺ React Hooks"]:::style1 --> B["π useState"]:::style2
A --> C["π useEffect"]:::style3
A --> D["π useRef"]:::style4
A --> E["β‘ useCallback"]:::style5
A --> F["π― useMemo"]:::style2
A --> G["π useContext"]:::style4
A --> H["π€ Custom Hooks"]:::style3
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
Happy coding! π
Getting Started with TypeScript in Vue 3 π
Vue 3 brings exciting features, especially when combined with TypeScript and the Composition API. Companies like Xiaomi and Laravel use Vue 3 with TypeScript for building scalable applications. This guide will help you understand the basics and benefits of using TypeScript in your Vue applications.
Key Concepts
1. defineComponent
This function helps you define a Vue component with TypeScript. It ensures type safety and better IntelliSense support.
2. ref<T>
Use ref to create reactive references. For example:
1
const count = ref<number>(0);
3. reactive
This function creates a reactive object. Itβs great for managing state:
1
const state = reactive({ name: 'Vue' });
4. computed
Use computed for derived state. It automatically updates when dependencies change:
1
const doubleCount = computed(() => count.value * 2);
5. Typing Props with PropType
You can define prop types easily:
1
2
3
4
5
6
7
8
import { PropType } from 'vue';
props: {
title: {
type: String as PropType<string>,
required: true,
},
}
Script Setup Syntax π οΈ
The <script setup> syntax simplifies component setup. It reduces boilerplate and enhances readability.
Benefits of TypeScript in Vue 3
- Type Safety: Catch errors early.
- Better Tooling: Enhanced IDE support.
- Improved Readability: Clearer code structure.
Example: Simple Counter Component
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<h1></h1>
<button @click="increment">Count: 9</button>
</div>
</template>
<script setup lang="ts">
import { ref, defineComponent } from 'vue';
const props = defineProps<{
title: string;
}>();
const count = ref<number>(0);
const increment = () => {
count.value++;
};
</script>
Guide to TypeScript in Angular π
Angular uses TypeScript by default, making it easier to build robust applications. Enterprise companies like Google, Microsoft, and IBM rely on Angular with TypeScript for mission-critical applications. Letβs explore some key aspects of TypeScript in Angular!
Component Typing π οΈ
In Angular, components are the building blocks of your application. You can define types for component properties and methods.
1
2
3
4
5
6
7
8
9
10
11
12
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
})
export class ExampleComponent {
title: string = 'Hello, Angular!';
count: number = 0;
increment(): void {
this.count++;
}
}
Template Type Checking π
Angular provides template type checking to catch errors in your HTML templates. This helps ensure that your bindings are correct.
Strict Templates Option βοΈ
Enable the strictTemplates option in your tsconfig.json to enhance type checking in templates:
1
2
3
4
5
{
"angularCompilerOptions": {
"strictTemplates": true
}
}
Typing Services and Dependency Injection ποΈ
When creating services, you can define types for injected dependencies:
1
2
3
4
5
6
7
8
@Injectable({
providedIn: 'root',
})
export class DataService {
getData(): Observable<string[]> {
return of(['Angular', 'TypeScript']);
}
}
RxJS Observables Typing π
Type your RxJS observables to ensure type safety:
1
2
3
this.dataService.getData().subscribe((data: string[]) => {
console.log(data);
});
Adding TypeScript to Svelte Projects π
TypeScript is a great way to add type safety to your Svelte projects! By using <script lang='ts'>, you can enjoy the benefits of TypeScript while building your components. Companies like Spotify and Apple use Svelte-like approaches for building performant UIs.
Why Use TypeScript in Svelte? π€
- Type Safety: Catch errors early with type checking.
- Better Tooling: Enjoy improved autocompletion and documentation in your IDE.
- Enhanced Readability: Clearer code with defined types.
Typing Props and Stores π¦
You can type your props and stores easily:
1
2
3
4
5
<script lang='ts'>
export let name: string;
import { writable } from 'svelte/store';
const count = writable<number>(0);
</script>
Reactive Statements and Event Handlers β‘
Type your reactive statements and event handlers for clarity:
1
2
3
4
5
6
7
<script lang='ts'>
let message: string = `Hello, ${name}!`;
function increment() {
count.update(n => n + 1);
}
</script>
Using svelte-check β
svelte-check helps you check types in your Svelte templates. Itβs super helpful for catching issues before they become bugs!
Example Component π
Hereβs a simple Svelte component using TypeScript:
1
2
3
4
5
6
7
8
9
10
11
12
<script lang='ts'>
export let name: string;
import { writable } from 'svelte/store';
const count = writable<number>(0);
function increment() {
count.update(n => n + 1);
}
</script>
<h1>Hello, {name}!</h1>
<button on:click={increment}>Count: {$count}</button>
Type-Safe Form Handling in TypeScript π
Handling forms in TypeScript can be fun and safe! Modern React applications use React Hook Form with Zod validation to reduce form-related bugs by up to 70%. Letβs explore how to use libraries like React Hook Form and Zod for validation.
Typing Form Values π
When creating forms, itβs essential to define the shape of your data. For example:
1
2
3
4
interface FormValues {
name: string;
email: string;
}
Validation Schemas β
Using Zod for runtime validation is a great choice. Hereβs how you can create a schema:
1
2
3
4
5
6
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email"),
});
Error Types β
You can handle errors easily with TypeScript. For example:
1
2
3
4
type FormErrors = {
name?: string;
email?: string;
};
Reusable Form Components β»οΈ
Creating reusable components makes your code cleaner. Hereβs a simple example:
1
2
3
4
5
6
7
const InputField: React.FC<{ label: string; register: any; error?: string }> = ({ label, register, error }) => (
<div>
<label>{label}</label>
<input {...register(label)} />
{error && <span>{error}</span>}
</div>
);
Flowchart of Form Handling π οΈ
graph TD
A["π Start"]:::style1 --> B["π Define Form Values"]:::style2
B --> C["β
Create Validation Schema"]:::style3
C --> D["π€ Handle Form Submission"]:::style4
D --> E["β Display Errors"]:::style5
E --> F["π Success"]:::style3
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
With these tools, you can create type-safe forms that are easy to manage and validate. Happy coding! π
Understanding Type-Safe State Management with TypeScript π οΈ
State management libraries like Redux Toolkit, Zustand, and Jotai help us manage our applicationβs state in a structured way. Large-scale applications at companies like Twitter and Airbnb use Redux with TypeScript to ensure type safety across thousands of components. When using TypeScript, we can make our state management type-safe, which means fewer bugs and clearer code! Letβs break it down.
Key Concepts π
1. Actions π
Actions are plain objects that describe what happened. In TypeScript, we can define them like this:
1
2
3
4
interface IncrementAction {
type: 'INCREMENT';
payload: number;
}
2. Reducers π
Reducers are functions that take the current state and an action, returning a new state. Hereβs a simple example:
1
2
3
4
5
6
7
8
const counterReducer = (state: number = 0, action: IncrementAction): number => {
switch (action.type) {
case 'INCREMENT':
return state + action.payload;
default:
return state;
}
};
3. Selectors π
Selectors help us get specific pieces of state. They can also be typed:
1
const selectCount = (state: { count: number }) => state.count;
4. Store State π¬
The store holds the state of your application. You can create a type-safe store like this:
1
2
3
4
5
6
7
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
Dispatching Typed Actions π
When dispatching actions, TypeScript ensures you use the correct types:
1
store.dispatch({ type: 'INCREMENT', payload: 1 });
Real-World Production Examples π’
1. React E-Commerce Product Card π¦
Netflix uses similar type-safe components to manage millions of streaming options:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
interface Product {
id: number;
name: string;
price: number;
rating: number;
}
const ProductCard: React.FC<{ product: Product; onAddToCart: (id: number) => Promise<void> }> = ({
product,
onAddToCart
}) => {
const [loading, setLoading] = React.useState<boolean>(false);
const handleClick = async () => {
setLoading(true);
try {
await onAddToCart(product.id);
} catch (err: unknown) {
const error = err as Error;
console.error('Add to cart failed:', error.message);
} finally {
setLoading(false);
}
};
return (
<div style=>
<h3>{product.name}</h3>
<p>Price: ${product.price}</p>
<p>Rating: {product.rating}/5</p>
<button onClick={handleClick} disabled={loading}>
{loading ? 'Adding...' : 'Add to Cart'}
</button>
</div>
);
};
2. Vue 3 Dashboard with Typed API π
Xiaomiβs Vue 3 dashboards rely on type-safe data fetching:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
interface DashboardMetrics {
users: number;
revenue: number;
orders: number;
}
const metrics = ref<DashboardMetrics | null>(null);
const loading = ref<boolean>(true);
const percentageGrowth = computed<number>(() => {
return metrics.value ? (metrics.value.revenue / 1000) * 100 : 0;
});
onMounted(async () => {
try {
const response = await fetch('/api/metrics');
metrics.value = await response.json();
} catch (err: unknown) {
console.error('Failed to fetch metrics:', err);
} finally {
loading.value = false;
}
});
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="metrics" class="dashboard">
<p>Users: </p>
<p>Revenue: $</p>
<p>Growth: %</p>
</div>
</template>
3. Angular HTTP Service with Error Handling π
Googleβs internal tools use Angular services like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
interface ApiResponse<T> {
data: T;
status: number;
}
@Injectable({ providedIn: 'root' })
export class ProductService {
constructor(private http: HttpClient) {}
getProducts(): Observable<ApiResponse<any[]>> {
return this.http.get<ApiResponse<any[]>>('/api/products').pipe(
catchError((err: Error) => {
console.error('API Error:', err.message);
return throwError(() => new Error('Failed to fetch products'));
})
);
}
}
4. Svelte Reactive Store ποΈ
Spotifyβs frontend uses Svelte stores for managing player state:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script lang="ts">
import { writable, derived } from 'svelte/store';
interface PlayerState {
playing: boolean;
volume: number;
currentTrack: string;
}
const playerStore = writable<PlayerState>({
playing: false,
volume: 100,
currentTrack: '',
});
const isLoud = derived(playerStore, $store => $store.volume > 70);
const togglePlayback = () => {
playerStore.update(state => ({
...state,
playing: !state.playing
}));
};
</script>
<button on:click={togglePlayback}>Toggle Play</button>
5. React Hook Form with Zod Validation π
Airbnbβs booking forms use this pattern for secure data handling:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { useForm, SubmitHandler } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
const registrationSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be 8+ characters'),
confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});
type RegistrationForm = z.infer<typeof registrationSchema>;
const RegistrationComponent: React.FC = () => {
const { register, handleSubmit, formState: { errors } } = useForm<RegistrationForm>({
resolver: zodResolver(registrationSchema),
});
const onSubmit: SubmitHandler<RegistrationForm> = async (data) => {
console.log('Form submitted:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} placeholder="Email" />
{errors.email && <span>{errors.email.message}</span>}
<input {...register('password')} type="password" placeholder="Password" />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Register</button>
</form>
);
};
6. Redux Toolkit Typed Shopping Cart π
Twitterβs e-commerce features rely on this pattern:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { createSlice, PayloadAction, configureStore } from '@reduxjs/toolkit';
interface CartItem {
id: number;
quantity: number;
price: number;
}
interface CartState {
items: CartItem[];
total: number;
}
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [], total: 0 } as CartState,
reducers: {
addItem: (state, action: PayloadAction<CartItem>) => {
state.items.push(action.payload);
state.total += action.payload.price * action.payload.quantity;
},
removeItem: (state, action: PayloadAction<number>) => {
const item = state.items.find(i => i.id === action.payload);
if (item) {
state.total -= item.price * item.quantity;
state.items = state.items.filter(i => i.id !== action.payload);
}
},
},
});
const store = configureStore({
reducer: { cart: cartSlice.reducer },
});
Hands-On Assignment: Build a Type-Safe E-Commerce Catalog App π
π Your Challenge: E-Commerce Catalog with TypeScript Frameworks
Conclusion: Master TypeScript with Frontend Frameworks π
TypeScript transforms frontend development from error-prone string manipulation into safe, self-documenting code with compile-time verification. By mastering type-safe components across React, Vue, Angular, and Svelte, implementing strongly-typed state management, building validated forms, and creating robust API integration layers, youβll build production-grade applications that scale effortlessly and maintain code quality as teams grow from 5 to 500+ developers.