| name | react-typescript |
| description | Complete React TypeScript system.
PROACTIVELY activate for: (1) Component props typing, (2) Event handler types, (3) Hooks with TypeScript, (4) Generic components, (5) forwardRef typing, (6) Context with type safety, (7) Utility types (Partial, Pick, Omit), (8) Discriminated unions for state.
Provides: Props interfaces, event types, generic patterns, type-safe context, polymorphic components.
Ensures type-safe React with proper TypeScript patterns.
|
Quick Reference
| Type | Usage | Example |
|---|
| Props interface | Component props | interface ButtonProps { variant: 'primary' } |
ReactNode | Children | children: ReactNode |
ChangeEvent | Input change | (e: ChangeEvent<HTMLInputElement>) |
FormEvent | Form submit | (e: FormEvent<HTMLFormElement>) |
MouseEvent | Click | (e: MouseEvent<HTMLButtonElement>) |
| Pattern | Example |
|---|
| Extend HTML props | extends ButtonHTMLAttributes<HTMLButtonElement> |
| Generic component | function List<T>({ items }: { items: T[] }) |
| forwardRef | forwardRef<HTMLInputElement, Props> |
| Discriminated union | { status: 'success'; data: T } | { status: 'error'; error: Error } |
| Utility Type | Purpose |
|---|
Partial<T> | All props optional |
Pick<T, K> | Select specific props |
Omit<T, K> | Exclude specific props |
ComponentProps<'button'> | Get element props |
When to Use This Skill
Use for React TypeScript integration:
- Typing component props and children
- Handling events with proper types
- Building generic reusable components
- Creating type-safe context and hooks
- Using utility types for prop manipulation
- Implementing polymorphic components
For React basics: see react-fundamentals-19
React with TypeScript
Component Props
Basic Props Types
function Greeting({ name, age }: { name: string; age: number }) {
return <p>Hello {name}, you are {age} years old</p>;
}
interface UserCardProps {
name: string;
email: string;
avatar?: string;
role: 'admin' | 'user' | 'guest';
}
function UserCard({ name, email, avatar, role }: UserCardProps) {
return (
<div className="user-card">
{avatar && <img src={avatar} alt={name} />}
<h3>{name}</h3>
<p>{email}</p>
<span className={`badge-${role}`}>{role}</>
);
}
= | | ;
= | | ;
= {
?: ;
?: ;
: .;
?: ;
};
() {
(
);
}
Children Props
import { ReactNode, PropsWithChildren } from 'react';
interface CardProps {
title: string;
children: ReactNode;
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
type ContainerProps = PropsWithChildren<{
className?: string;
}>;
function Container({ className, children }: ContainerProps) {
return <div className={className}>{children}</div>;
}
interface DataFetcherProps<T> {
url: string;
children: (data: T, loading: ) => ;
}
<T>({ url, children }: <T>) {
[data, setData] = useState<T | >();
[loading, setLoading] = ();
;
}
Extending HTML Element Props
import { ButtonHTMLAttributes, InputHTMLAttributes, forwardRef } from 'react';
interface CustomButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
isLoading?: boolean;
}
const CustomButton = forwardRef<HTMLButtonElement, CustomButtonProps>(
({ variant = 'primary', isLoading, children, className, disabled, ...props }, ref) => {
return (
<button
ref={ref}
className={`btn btn-${variant} ${className || ''}`}
disabled={disabled || isLoading}
{...props}
>
{isLoading ? 'Loading...' : children}
</button>
);
}
);
CustomButton.displayName = 'CustomButton';
interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
: ;
?: ;
?: | | ;
}
= forwardRef<, >(
{
(
);
}
);
. = ;
Polymorphic Components
import { ElementType, ComponentPropsWithoutRef, ReactNode } from 'react';
type PolymorphicProps<E extends ElementType> = {
as?: E;
children: ReactNode;
} & Omit<ComponentPropsWithoutRef<E>, 'as' | 'children'>;
function Box<E extends ElementType = 'div'>({
as,
children,
...props
}: PolymorphicProps<E>) {
const Component = as || 'div';
return <Component {...props}>{children}</Component>;
}
function App() {
return (
<>
<Box>Default div</Box>
<Box as="section" className="section">Section element</Box>
<Box = =>Link element
console.log('clicked')}>Button
);
}
Event Handlers
Common Event Types
import {
ChangeEvent,
FormEvent,
MouseEvent,
KeyboardEvent,
FocusEvent,
DragEvent,
} from 'react';
function EventExamples() {
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSelectChange = (e: ChangeEvent<HTMLSelectElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
console.log(Object.fromEntries(formData));
};
= () => {
.(e., e.);
};
= () => {
(e. === ) {
.();
}
};
= () => {
.(, e..);
};
= () => {
e..(, );
};
(
);
}
Event Handler Props
interface FormFieldProps {
onChange: (value: string) => void;
onBlur?: () => void;
}
function FormField({ onChange, onBlur }: FormFieldProps) {
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value);
};
return <input onChange={handleChange} onBlur={onBlur} />;
}
interface ListItemProps<T> {
item: T;
onSelect: (item: T) => void;
onDelete?: (item: T) => void;
}
function ListItem<T extends { id: string; name: string }>({
item,
onSelect,
onDelete,
}: ListItemProps<T>) {
return (
);
}
Hooks with TypeScript
useState
import { useState } from 'react';
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
type Status = 'idle' | 'loading' | 'success' | 'error';
const [status, setStatus] = useState<Status>('idle');
interface FormState {
name: string;
email: string;
errors: Record<string, string>;
}
const [form, setForm] = useState<FormState>({
name: '',
email: '',
errors: {},
});
setForm(prev => ({ ...prev, name: 'John' }));
useReducer
import { useReducer, Reducer } from 'react';
interface CounterState {
count: number;
step: number;
}
type CounterAction =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
| { type: 'setStep'; payload: number };
const counterReducer: Reducer<CounterState, CounterAction> = (state, action) => {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'reset':
return { ...state, count: 0 };
case 'setStep':
return { ...state, : action. };
:
state;
}
};
() {
[state, dispatch] = (counterReducer, { : , : });
(
);
}
useRef
import { useRef, useEffect } from 'react';
function RefExamples() {
const inputRef = useRef<HTMLInputElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const countRef = useRef<number>(0);
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
inputRef.current?.focus();
const ctx = canvasRef.current?.getContext('2d');
if (ctx) {
ctx.fillRect(0, 0, 100, 100);
}
timerRef.current = setInterval(() => {
countRef.current += 1;
}, 1000);
return () => {
if (timerRef.current) {
(timerRef.);
}
};
}, []);
(
);
}
useContext
import { createContext, useContext, useState, ReactNode } from 'react';
interface Theme {
primary: string;
secondary: string;
mode: 'light' | 'dark';
}
interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
toggleMode: () => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>({
primary: '#007bff',
secondary: '#6c757d',
mode: 'light',
});
const toggleMode = () => {
setTheme((prev) => ({
...prev,
mode: prev.mode === ? : ,
}));
};
(
);
}
() {
context = ();
(!context) {
();
}
context;
}
() {
{ theme, toggleMode } = ();
(
);
}
Custom Hooks
import { useState, useEffect, useCallback } from 'react';
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = response.();
(result);
} (err) {
(err ? err : ());
} {
();
}
}, [url]);
( {
();
}, [fetchData]);
{ data, loading, error, : fetchData };
}
{
: ;
: ;
: ;
}
() {
{ : user, loading, error } = useFetch<>();
(loading) ;
(error) ;
(!user) ;
(
);
}
Generic Components, Type Utilities & Type-Safe Context
Full code for generic components (List<T>, Select<T>, Table<T>), utility types (Partial, Pick, Omit, Record, Extract, Exclude, ComponentProps, ComponentPropsWithRef, ComponentPropsWithoutRef), discriminated unions for API states, inference / conditional types (infer, Awaited, PropsOf), and the type-safe Context factory pattern (createSafeContext) lives in references/generics-utilities-context.md. Load that reference when building reusable typed components, working with React's prop-type utilities, or wiring a strongly-typed Context provider.
Best Practices
| Practice | Example |
|---|
| Use interface for component props | interface ButtonProps { ... } |
| Prefer type inference when obvious | useState(0) vs useState<number>(0) |
| Use generics for reusable components | List<T>, Select<T> |
| Discriminated unions for state | { status: 'success'; data: T } |
| forwardRef with proper types | forwardRef<HTMLButtonElement, Props> |
Avoid any, use unknown if needed | catch (err: unknown) |
Use as const for literal types | ['a', 'b'] as const |