| name | react-components |
| description | Build production-ready React components with TypeScript. Use when creating UI components (buttons, inputs, modals, cards), implementing hooks patterns, component composition, or setting up component documentation/testing. Covers accessibility, error boundaries, and performance optimization. |
React Components
Component Patterns
Presentational vs Container
function UserCard({ name, avatar }: { name: string; avatar: string }) {
return <div><img src={avatar} alt="" /><span>{name}</span></div>;
}
function UserCardContainer({ userId }: { userId: string }) {
const { data, isLoading } = useUser(userId);
if (isLoading) return <Skeleton />;
return <UserCard name={data.name} avatar={data.avatar} />;
}
Props Interface Design
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', isLoading, children, ...props }, ref) => (
<button ref={ref} disabled={isLoading} {...props}>{children}</button>
)
);
Button.displayName = 'Button';
Compound Components
const Card = ({ children }: { children: ReactNode }) => (
<div className="card">{children}</div>
);
Card.Header = ({ children }: { children: ReactNode }) => (
<div className="card-header">{children}</div>
);
Card.Body = ({ children }: { children: ReactNode }) => (
<div className="card-body">{children}</div>
);
Hooks Best Practices
useState
const [state, setState] = useState(() => computeExpensiveValue());
setState(prev => prev + 1);
useEffect
useEffect(() => {
const subscription = api.subscribe(handler);
return () => subscription.unsubscribe();
}, [handler]);
useEffect(() => {
const options = { serverUrl, roomId };
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]);
useCallback/useMemo
const handleSubmit = useCallback((data: FormData) => {
submitForm(data, userId);
}, [userId]);
const sortedItems = useMemo(
() => items.slice().sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
const contextValue = useMemo(() => ({ user, login }), [user, login]);
Custom Hooks
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
Error Boundary
import { Component, type ReactNode, type ErrorInfo } from 'react';
interface Props { children: ReactNode; fallback?: ReactNode; }
interface State { hasError: boolean; }
class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('Error:', error, info.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <div>Something went wrong.</div>;
}
return this.props.children;
}
}
Accessibility
Required Patterns
<button aria-label="Close dialog">×</button>
<button aria-busy={isLoading} disabled={isLoading}>Submit</button>
<label htmlFor={id}>{label}</label>
<input id={id} aria-invalid={!!error} aria-describedby={errorId} />
{error && <span id={errorId} role="alert">{error}</span>}
<div role="dialog" aria-modal="true" aria-labelledby="modal-title">
<h2 id="modal-title">Dialog Title</h2>
</div>
Keyboard Navigation
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
Resources
Templates
Copy from assets/ directory:
Button.tsx - Accessible button with variants, sizes, loading state
Input.tsx - Form input with labels, validation, addons
Modal.tsx - Accessible modal with focus trap, portal
Card.tsx - Card with compound components pattern
Documentation