React development
Skill bg-szy/TOP-SKILLS/skills/agent-skills/react-development
全球最大的 Claude Code 技能聚合库 · 收录 3900+ 来自 12+ 来源的技能,提供在线搜索与趋势分析看板 / The world's largest Claude Code skill aggregation hub — 3900+ skills from 12+ sources with online search and trend dashboard
npx -y skills add bg-szy/TOP-SKILLS --skill react-developmentAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
React 19+ with TypeScript — hooks, custom hooks, state management (useState/useReducer/useContext), React Query/SWR, Tailwind CSS, performance. Use when building React components, apps, or optimizing renders.
SKILL.md
30.5 KB, as published. Nobody here has run it
React Development
Optimized for React 19+, TypeScript 5.5+, React Testing Library 16+, and modern server-first app architectures.
Expert guidance for building high-quality React applications with React 19+, modern hooks, TypeScript, and best practices following official React documentation at https://react.dev.
- Leverage native parallel subagent dispatch and 200k+ context windows where available.
Component Review Rubric Reference
Apply the shared Component Review Rubric before approving React components, then run the React-specific checks below.
Anti-Patterns
- Fetching everything in client effects by default: Server-first data loading is usually simpler, faster, and easier to cache.
- Adding memoization before profiling: Manual optimizations can create stale-prop bugs and hide simpler design fixes.
- Skipping keyboard and accessible-name review: A component is not done if only pointer users can operate it reliably.
Verification Protocol
Before claiming "skill applied successfully":
- Pass/fail: The React Development guidance is tied to a concrete route, component, screen, or design artifact.
- Pass/fail: Component states cover loading, empty, error, success, and responsive breakpoints where applicable.
- Pass/fail: Accessibility, visual hierarchy, and interaction behavior are reviewed against the shared component rubric.
- Pressure-test scenario: Review the component on a narrow mobile viewport, keyboard-only path, and slow-loading state.
- Success metric: Zero generic UI approval; every approval cites rendered behavior or source evidence.
Before and After Example
// Before
'use client';
export function Dashboard() {
const [orders, setOrders] = useState<Order[]>([]);
useEffect(() => {
fetch('/api/orders').then((r) => r.json()).then(setOrders);
}, []);
return <OrdersTable orders={orders} />;
}
// After
export async function Dashboard() {
const orders = await getOrders();
return <OrdersTable orders={orders} />;
}
Lets the server boundary load data directly and keeps the client side focused on interaction, not bootstrapping.
Activation Conditions
Use symptom -> action triggers: when one matches, apply this skill and verify with the protocol below.
Core React Development:
- Building React components with hooks and TypeScript
- Setting up React applications (Vite, Next.js, CRA)
- Working with React Router for navigation
- Implementing forms and user input handling
- Creating responsive layouts and component architecture
State Management & Data:
- Managing state (useState, useReducer, useContext)
- Implementing React Query or SWR for data fetching
- Building custom hooks for reusable stateful logic
- Creating Context providers for global state
- Implementing React 19 Server Components (if using Next.js)
Component Patterns:
- Creating reusable UI components with proper composition
- Building forms with React Hook Form and Zod validation
- Implementing modal dialogs and overlays
- Creating custom React hooks (useDebounce, useLocalStorage)
- Designing responsive variants for different screen sizes
Performance & Quality:
- Optimizing React app performance
- Implementing memoization (useMemo, useCallback)
- Code splitting and lazy loading
- Writing tests with React Testing Library
- Ensuring accessibility compliance (ARIA, keyboard nav)
Part 1: React Fundamentals
Project Structure
Recommended Structure:
src/
├── components/ # Reusable UI components
│ ├── ui/ # Primitives (Button, Input, Modal)
│ ├── forms/ # Form components
│ └── layout/ # Layout components
├── hooks/ # Custom hooks
├── contexts/ # Context providers
├── pages/ # Page components
├── lib/ # Utilities, API clients
├── types/ # TypeScript types
├── styles/ # Global styles
└── main.jsx # App entry point
Component Architecture
Functional Components (React 19 Standard)
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
children: React.ReactNode;
disabled?: boolean;
isLoading?: boolean;
}
export function Button({
variant = 'primary',
size = 'md',
onClick,
children,
disabled = false,
isLoading = false,
}: ButtonProps) {
const baseClasses = 'rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';
const variantClasses = {
primary: 'bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500',
secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-900 focus:ring-gray-500',
danger: 'bg-red-600 hover:bg-red-700 text-white focus:ring-red-500',
};
const sizeClasses = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-5 py-2.5 text-lg',
};
return (
<button
disabled={disabled || isLoading}
onClick={onClick}
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
>
{isLoading ? 'Loading...' : children}
</button>
);
}
Generic Components
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
emptyMessage?: string;
}
function List<T>({ items, renderItem, keyExtractor, emptyMessage = 'No items' }: ListProps<T>) {
return (
<ul>
{items.length === 0 ? (
<li>{emptyMessage}</li>
) : (
items.map((item, index) => (
<li key={keyExtractor(item)}>
{renderItem(item, index)}
</li>
))
)}
</ul>
);
}
// Usage
<List
items={users}
renderItem={(user) => `${user.firstName} ${user.lastName}`}
keyExtractor={(user) => user.id}
/>
Component Patterns
Composition over Inheritance:
// ✅ GOOD - Component composition
function Card({ children }: { children: React.ReactNode }) {
return (
<div className="border rounded-lg p-4 shadow-sm">
{children}
</div>
);
}
function CardHeader({ children }: { children: React.ReactNode }) {
return (
<div className="font-bold text-lg mb-2 border-b pb-2">
{children}
</div>
);
}
function CardBody({ children }: { children: React.ReactNode }) {
return <div>{children}</div>;
}
// Usage
<Card>
<CardHeader>Title</CardHeader>
<CardBody>Content here</CardBody>
</Card>
// ❌ BAD - Inheritance or complex props
function Card({ renderHeader, renderBody, renderFooter }: { ... }) { ... }
Presentational vs Container Components:
// Presentational - UI only, doesn't fetch data
interface UserCardProps {
user: User;
onStatusChange: (status: string) => void;
}
function UserCard({ user, onStatusChange }: UserCardProps) {
return (
<div>
<h3>{user.name}</h3>
<select value={user.status} onChange={(e) => onStatusChange(e.target.value)}>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
);
}
// Container - Fetches data, passes to presentational
function UserContainer() {
const { data: user, mutate: updateStatus } = useUser(userId);
return user ? (
<UserCard user={user} onStatusChange={(status) => updateStatus({ status })} />
) : (
<LoadingSpinner />
);
}
Part 2: Hooks Patterns
Built-in Hooks Mastery
useState - For Local State
// Simple state
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
// Complex state - use useReducer instead
function Form() {
type FormState = {
name: string;
email: string;
submitted: boolean;
};
const [form, setForm] = useState<FormState>({
name: '',
email: '',
submitted: false,
});
const updateField = (field: keyof FormState) => (
e: React.ChangeEvent<HTMLInputElement>
) => setForm(f => ({ ...f, [field]: e.target.value }));
return (
<form>
<input value={form.name} onChange={updateField('name')} />
<input value={form.email} onChange={updateField('email')} />
</form>
);
}
useReducer - For Complex State
type Action =
| { type: 'ADD_TODO'; payload: string }
| { type: 'TOGGLE_TODO'; payload: number }
| { type: 'DELETE_TODO'; payload: number };
type TodoState = {
items: Array<{ id: number; text: string; completed: boolean }>;
};
function todosReducer(state: TodoState, action: Action): TodoState {
switch (action.type) {
case 'ADD_TODO':
return {
items: [
...state.items,
{ id: Date.now(), text: action.payload, completed: false },
],
};
case 'TOGGLE_TODO':
return {
items: state.items.map((item) =>
item.id === action.payload ? { ...item, completed: !item.completed } : item
),
};
case 'DELETE_TODO':
return {
items: state.items.filter((item) => item.id !== action.payload),
};
default:
return state;
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todosReducer, { items: [] });
return (
<div>
<ToDoList items={state.items} onToggle={(id) => dispatch({ type: 'TOGGLE_TODO', payload: id })} />
</div>
);
}
useEffect - For Side Effects
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
// ✅ GOOD - Run on mount and when userId changes
useEffect(() => {
let cancelled = false;
async function fetchUser() {
const userData = await api.getUser(userId);
if (!cancelled) {
setUser(userData);
}
}
fetchUser();
return () => {
cancelled = true; // Cleanup to prevent state updates after unmount
};
}, [userId]);
// ✅ GOOD - Document title update
useEffect(() => {
if (user) {
document.title = `${user.name} - Profile`;
}
return () => {
document.title = 'App';
};
}, [user]);
if (!user) return <LoadingSkeleton />;
return (
<div>
<h1>{user.name}</h1>
{/* ... */}
</div>
);
}
useContext - For Global State
// Context creation
type ThemeContextType = {
theme: 'light' | 'dark';
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeContextType | null>(null);
// Provider component
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook for using context
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Usage in component
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className={theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-black'}
>
Toggle {theme} theme
</button>
);
}
Performance Hooks
useMemo - Memoize Expensive Computations
function ProductList({ products }: { products: Product[] }) {
// ✅ GOOD - Memoize expensive calculation
const sortedProducts = useMemo(() => {
return [...products].sort((a, b) => a.price - b.price);
}, [products]);
const expensiveAnalysis = useMemo(() => {
// Only recompute when prices change
const avgPrice = products.reduce((sum, p) => sum + p.price, 0) / products.length;
const maxPrice = Math.max(...products.map(p => p.price));
return { avgPrice, maxPrice };
}, [products]);
return (
<div>
<StatsCard data={expensiveAnalysis} />
<ProductCards products={sortedProducts} />
</div>
);
}
useCallback - Memoize Functions
function ParentComponent() {
const [items, setItems] = useState<Item[]>([]);
// ✅ GOOD - Memoize callback to prevent child re-renders
const handleItemAdd = useCallback((item: Item) => {
setItems(prev => [...prev, item]);
}, []);
const handleItemRemove = useCallback((id: string) => {
setItems(prev => prev.filter(item => item.id !== id));
}, []);
return (
<div>
<AddItemForm onAdd={handleItemAdd} />
<ItemList items={items} onRemove={handleItemRemove} />
</div>
);
}
// Child component wrapped in React.memo
const ItemList = React.memo(function ItemList({
items,
onRemove,
}: {
items: Item[];
onRemove: (id: string) => void;
}) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={() => onRemove(item.id)}>Remove</button>
</li>
))}
</ul>
);
});
Part 3: Custom Hooks
useDebounce
Debounce rapid value changes, useful for search inputs:
import { useState, useEffect } from 'react';
function useDebounce<T>(value: T, delay: number = 500): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// Usage
function SearchBar() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
// This only runs 300ms after user stops typing
if (debouncedQuery) {
performSearch(debouncedQuery);
}
}, [debouncedQuery]);
return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}
useLocalStorage
Persist state to localStorage:
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
};
return [storedValue, setValue] as const;
}
// Usage
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}
useToggle
Toggle boolean state with useful utilities:
function useToggle(initialValue: boolean = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse, setValue } as const;
}
// Usage
function ModalExample() {
const { value: isOpen, toggle, setFalse: close } = useToggle(false);
return (
<>
<button onClick={toggle}>Open Modal</button>
<Modal isOpen={isOpen} onClose={close}>
<p>Modal content</p>
</Modal>
</>
);
}
useFetch
Data fetching with loading and error states:
type 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);
const jsonData = await response.json();
setData(jsonData);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
}, [url]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
// Usage
function UserProfile({ userId }: { userId: string }) {
const { data: user, loading, error } = useFetch<User>(`/api/users/${userId}`);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorAlert message={error.message} />;
return (
<div>
<h1>{user?.name}</h1>
<p>{user?.email}</p>
</div>
);
}
Part 4: Forms & Validation
React Hook Form with Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Zod schema for validation
const formSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
age: z.number().min(18, 'Must be at least 18'),
subscribe: z.boolean().default(false),
});
type FormData = z.infer<typeof formSchema>;
function UserForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>({
resolver: zodResolver(formSchema),
});
const onSubmit = async (data: FormData) => {
try {
await api.createUser(data);
alert('User created successfully!');
} catch (error) {
alert('Error creating user');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-4">
<label>Name</label>
<input {...register('name')} />
{errors.name && <span className="error">{errors.name.message}</span>}
</div>
<div className="mb-4">
<label>Email</label>
<input type="email" {...register('email')} />
{errors.email && <span className="error">{errors.email.message}</span>}
</div>
<div className="mb-4">
<label>Age</label>
<input type="number" {...register('age', { valueAsNumber: true })} />
{errors.age && <span className="error">{errors.age.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Creating...' : 'Create User'}
</button>
</form>
);
}
Form Components
interface FormFieldProps {
label: string;
error?: string;
children: React.ReactNode;
}
function FormField({ label, error, children }: FormFieldProps) {
return (
<div className="mb-4">
<label className="block text-sm font-medium mb-1">{label}</label>
{children}
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
</div>
);
}
// Usage
<FormField label="Email" error={errors.email?.message}>
<input
type="email"
{...register('email')}
className="w-full px-3 py-2 border rounded"
/>
</FormField>
Part 5: Tailwind CSS Integration
Utility Classes for React Components
// Button component with variants
const buttonVariants = {
primary: 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-500',
secondary: 'bg-gray-200 hover:bg-gray-300 focus:ring-gray-500',
danger: 'bg-red-600 hover:bg-red-700 focus:ring-red-500',
ghost: 'bg-transparent hover:bg-gray-100 focus:ring-gray-500',
};
const buttonSizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-5 py-2.5 text-lg',
};
export function Button({
variant = 'primary',
size = 'md',
className = '',
children,
...props
}: ButtonProps) {
return (
<button
className={cn(
'rounded-lg font-medium transition-colors focus:outline-none focus:ring-2',
buttonVariants[variant],
buttonSizes[size],
className
)}
{...props}
>
{children}
</button>
);
}
Responsive Variants
interface CardProps {
isFeatured?: boolean;
children: React.ReactNode;
}
export function Card({ isFeatured = false, children }: CardProps) {
return (
<div
className={cn(
'rounded-lg shadow-md p-6 transition-all',
'bg-white hover:shadow-lg',
// Responsive padding
'sm:p-4 md:p-6 lg:p-8',
// Featured highlight
isFeatured && 'ring-2 ring-blue-500 border-2 border-blue-500'
)}
>
{children}
</div>
);
}
Part 6: State Management Patterns
Context API for Global State
// AppContext.tsx
interface AppState {
user: User | null;
loading: boolean;
login: (credentials: LoginCredentials) => Promise<void>;
logout: () => void;
}
const AppContext = createContext<AppState | null>(null);
export function AppProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const login = async (credentials: LoginCredentials) => {
setLoading(true);
try {
const userData = await api.login(credentials);
setUser(userData);
} finally {
setLoading(false);
}
};
const logout = () => {
api.logout();
setUser(null);
};
return (
<AppContext.Provider value={{ user, loading, login, logout }}>
{children}
</AppContext.Provider>
);
}
export function useApp() {
const context = useContext(AppContext);
if (!context) throw new Error('useApp must be used within AppProvider');
return context;
}
Using React Query (TanStack Query)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Fetch data
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => api.getUser(userId),
staleTime: 5 * 60 * 1000, // 5 minutes
});
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorAlert message={error.message} />;
return (
<div>
<h1>{user?.name}</h1>
<p>{user?.email}</p>
</div>
);
}
// Mutate data
function UpdateUserForm({ user: initialUser }: { user: User }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: api.updateUser,
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['user', initialUser.id] });
},
});
const handleSubmit = (data: Partial<User>) => {
mutation.mutate({ ...initialUser, ...data });
};
return (
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(/* data */); }}>
{/* Form fields */}
</form>
);
}
Part 7: Performance Optimization
Code Splitting with React.lazy
import { lazy, Suspense } from 'react';
// Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Reports = lazy(() => import('./pages/Reports'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/reports" element={<Reports />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
Virtual Scrolling
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // Estimated item height
overscan: 5, // Render extra items beyond viewport
});
return (
<div ref={parentRef} className="h-96 overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => {
const item = items[virtualItem.index];
return (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{/*
...existing code...
*/}
</div>
);
})}
</div>
</div>
);
}
Part 8: Testing with React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { expect } from 'vitest';
import userEvent from '@testing-library/user-event';
describe('Button Component', () => {
it('renders children correctly', () => {
render(<Button>Click Me</Button>);
expect(screen.getByText('Click Me')).toBeInTheDocument();
});
it('calls onClick handler when clicked', async () => {
const user = userEvent.setup();
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click Me</Button>);
await user.click(screen.getByText('Click Me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Cannot Click</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
describe('UseFetch Hook', () => {
it('fetches data and returns it', async () => {
const { result } = renderHook(() => useFetch('/api/test'));
await waitFor(() => {
expect(result.current.data).toBeDefined();
expect(result.current.loading).toBe(false);
});
});
});
Modern Component and Testing Examples
Server Components
export async function OrdersPanel() {
const orders = await getOrders();
return <OrdersTable orders={orders} />;
}
Error Boundaries
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary fallbackRender={() => <p>Could not load dashboard.</p>}>
<Dashboard />
</ErrorBoundary>
Accessibility Testing Tools
import { axe } from 'jest-axe';
test('dialog passes axe checks', async () => {
const { container } = render(<AccountDialog open />);
expect(await axe(container)).toHaveNoViolations();
});
React Development Best Practices
Components
- Use functional components with hooks
- Keep components small and focused
- Use TypeScript for type safety
- Implement proper error boundaries
- Add loading and error states
Hooks
- Follow rules of hooks (only call at top level)
- Use custom hooks for reusable stateful logic
- Memoize expensive computations and callbacks
- Clean up side effects properly
Performance
- Implement code splitting for large bundles
- Use virtual scrolling for long lists
- Debounce/throttle user input
- Lazy load images and resources
Accessibility
- Use semantic HTML elements
- Ensure keyboard navigation works
- Add ARIA labels where needed
- Support screen readers properly
- Test with accessibility tools
Common Pitfalls
- Fetching everything in client effects by default: Server-first data loading is usually simpler, faster, and easier to cache.
- Overusing memoization without a profiler: Manual memoization adds complexity and can hide stale-prop bugs.
- Skipping accessible names and keyboard flows: Components can appear done while still failing real user interaction paths.
References & Resources
Documentation
- Hooks Reference — All 17 React hooks including React 19 new hooks (use, useFormStatus, useOptimistic)
- Patterns Catalog — 12 React component patterns with TypeScript examples
Scripts
- Component Generator — PowerShell component scaffolder with types: functional, page, layout, context
Examples
- Custom Hooks Gallery — 12 production-ready custom hooks with full implementations and tests
<!-- PORTABILITY:START -->
Cross-Client Portability
This skill is written to stay usable across GitHub Copilot, Claude Code, Codex, and Gemini CLI.
- GitHub Copilot: keep the folder in a Copilot-visible skill or plugin path, or wrap the workflow as project instructions if the host does not support portable skill folders directly.
- Claude Code: keep the folder in a local skills directory or a compatible plugin or marketplace source.
- Codex: install or sync the folder into
$CODEX_HOME/skills/<skill-name>and restart Codex after major changes. - Gemini CLI: this repository generates a project command named
/skills:react-developmentfrom this skill. Rebuild commands withpython scripts/export-gemini-skill.py react-developmentand then run/commands reloadinside Gemini CLI.
MCP Availability And Fallback
Preferred MCP Server: None required
- Fallback prompt: "Use the React Development skill without MCP. Rely on the local
SKILL.md, bundled references or scripts, and manual verification. Show the exact commands, evidence, and final checks you used before concluding." - If the current host does not expose a matching server, use the bundled references, scripts, native toolchain, and manual workflow already described in this skill.
- Treat direct local verification, rendered output, logs, tests, or screenshots as the fallback evidence path before completion.
Related Skills
- javascript-development: Use it when the workflow also needs modern JavaScript and TypeScript application code.
- nextjs-development: Use it when the workflow also needs Next.js App Router and server-first React patterns.
- web-testing: Use it when the workflow also needs browser and end-to-end testing evidence.
- code-quality: Use it when the workflow also needs two-stage review (spec compliance first, then code quality), maintainability, and refactoring guidance.