1. Interfaces for Component Props
interface ProductProps {
name: string;
price: number;
imageUrl: string;
inStock: boolean;
}
const ProductCard: React.FC<ProductProps> = ({ name, price, imageUrl, inStock }) => {
return (
<div>
<h2>{name}</h2>
<img src={imageUrl} alt={name} />
<p>Price: ${price}</p>
{inStock ? <p>In Stock</p> : <p>Out of Stock</p>}
</div>
);
};
- The ProductProps interface enforces structure and type safety for props passed to the ProductCard component.
- This ensures consistency, prevents runtime type errors, and enhances code readability.
2. Generics for Reusable Components
interface ItemData<T> {
id: number;
value: T;
}
const ListComponent: React.FC<{ data: ItemData<any>[] }> = ({ data }) => {
return (
<ul>
{data.map((item) => (
<li key={item.id}>{item.value}</li>
))}
</ul>
);
};
- The ListComponent leverages generics () to work with various data types (numbers, strings, custom objects, etc.)
- Example usage: <ListComponent data={[{id: 1, value: 'Product A'}]} />
3. Typing React Hooks
const [count, setCount] = useState<number>(0);
const handleIncrement = () => {
setCount(count + 1);
};
- useState specifies that the count state variable holds a number.
- TypeScript ensures type-correct updates and usage throughout the component.
4. Typing Events
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setNewValue(event.target.value);
};
return (
<input type="text" value={newValue} onChange={handleInputChange} />
);
- The event object is accurately typed for type-safe access to input values.
1. Union Types for Flexible Props
interface ButtonProps {
label: string;
onClick: () => void;
size?: 'small' | 'medium' | 'large'; // Optional size property
variant?: 'primary' | 'secondary'; // Optional variant
}
const Button: React.FC<ButtonProps> = ({ label, onClick, size, variant }) => {
// ... button rendering with size and variant styles
};
- Union types (|) allow multiple possible values for props, boosting component adaptability.
2. Conditional Types for Dynamic Typing
type FetchStatus<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
const useFetchData = <T>(): FetchStatus<T> => {
// ... data fetching logic
};
- Conditional types change the resulting type based on logic within the type definition, perfect for modeling states like fetch results.
3. Utility Types for Type Manipulation
type FormValues = {
name: string;
email: string;
}
const initialValues: Partial<FormValues> = {}; // all properties become optional
const readonlyProps: Readonly<FormValues> = {
name: 'Alice',
email: 'alice@example.com'
}; // all properties are read-only
- Partial makes object properties optional.
- Readonly prevents property modification.
Key Idea: Sometimes a component prop might accept several discrete values. Union types let us accurately model and handle these scenarios.
Example Breakdown:
- The
size
prop can be'small'
,'medium'
,'large'
, or evenundefined
when not explicitly set. Union types capture this flexibility. - The
variant
prop has similar behavior.
Benefits:
- Developers using the
Button
component get clear type hints about valid prop values. - TypeScript will warn if you try to pass an invalid value (e.g.,
size='extra-large'
). - Your component's logic can handle these variations in a type-safe manner using conditional checks (e.g.,
if (size == 'large') { ... }
).
Key Idea: Conditional types are like "if statements" within TypeScript's type system, allowing you to express logic when defining types.
Example Breakdown:
FetchStatus<T>
represents the different states a data-fetching operation can be in:'idle'
: No fetch in progress.'loading'
: Fetch is ongoing.'success'
: Fetch succeeded, anddata
holds the result of typeT
.'error'
: The fetch failed, anderror
holds the Error object.
Benefits:
- The return type of
useFetchData
accurately reflects the component's state at any given time. - TypeScript will prevent you from trying to access
data
when the status is not'success'
.
Key Idea: TypeScript offers built-in utility types that streamline common type transformations.
Example Breakdown:
Partial<FormValues>
creates a type where every property fromFormValues
becomes optional. This is great for default values or incomplete form submission scenarios.Readonly<FormValues>
makes every property inFormValues
read-only. This is useful when you need to pass form data around but prevent accidental modifications.
Benefits:
- Avoids tedious manual type definitions.
- Promotes type safety and maintainability by signaling your intent (e.g., indicating immutable data).
1. Union Types for Flexible Form Controls TypeScript
interface FormFieldProps {
label: string;
name: string;
type?: 'text' | 'email' | 'password' | 'textarea' | 'select';
}
const FormField: React.FC<FormFieldProps> = ({ label, name, type = 'text', ...otherProps }) => {
// ... Logic to render the appropriate input/select/textarea based on 'type' prop
};
- This makes a single FormField component handle various input types.
- Your form code becomes more maintainable, avoiding redundant, type-specific components.
2. Conditional Types for API Responses
// Example API response
interface Product {
id: number;
name: string;
// ... other product details
}
type ProductFetchStatus =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: Product[] } // Success with array of products
| { status: 'error'; error: Error };
const useFetchProducts = (): ProductFetchStatus => {
// ... logic to fetch products from an API
};
- The ProductFetchStatus accurately models the state of fetching products from an API.
- Your component can gracefully handle loading, error, and success cases based on the status, providing a type-safe and user-friendly experience.
3. Utility Types for Complex State Management
interface Todo {
id: number;
text: string;
completed: boolean;
}
const initialTodos: Readonly<Todo[]> = [/* ... */];
// Reducer actions with payloads using utility types
type AddTodoAction = { type: 'ADD_TODO'; payload: Pick<Todo, 'text'> };
type ToggleTodoAction = { type: 'TOGGLE_TODO'; payload: Pick<Todo, 'id'> };
- Readonly<Todo[]> ensures your initial state cannot be accidentally modified.
- Pick assists in constructing action payloads by selecting specific properties from existing types, improving reducer code clarity and type safety.
Example: a component that fetches a list of products from an API and displays them, handling loading and error states
import React, { useState, useEffect } from 'react';
// Simplified API interaction (you'd likely use axios or fetch)
const fetchProducts = async (): Promise<Product[]> => {
// Simulate delayed fetch
await new Promise((resolve) => setTimeout(resolve, 1000));
// Replace with your actual API call
return [
{ id: 1, name: 'Product A', price: 9.99 },
{ id: 2, name: 'Product B', price: 14.99 },
];
};
// ... Product type definition from earlier
type ProductFetchStatus =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: Product[] }
| { status: 'error'; error: Error };
const ProductList: React.FC = () => {
const [status, setStatus] = useState<ProductFetchStatus>({ status: 'idle' });
useEffect(() => {
const fetchData = async () => {
setStatus({ status: 'loading' });
try {
const data = await fetchProducts();
setStatus({ status: 'success', data });
} catch (error) {
setStatus({ status: 'error', error });
}
};
fetchData();
}, []);
return (
<div>
{status.status === 'loading' && <p>Loading...</p>}
{status.status === 'success' && (
<ul>
{status.data.map((product) => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
)}
{status.status === 'error' && <p>Error: {status.error.message}</p>}
</div>
);
};
export default ProductList;
- Conditional Type: ProductFetchStatus models the possible states of the fetch operation.
- State Management: useState tracks the fetch status.
- Fetching Logic: useEffect handles fetching data on component mount, updating state through the various stages.
- Conditional Rendering: The JSX conditionally renders elements based on the status, providing loading feedback, an error message, or the fetched product list.
Key Benefit: The combination of TypeScript and React's state management allows your component to clearly represent the flow of data fetching, enhancing type safety and user experience.