SPFx React Components: Reusable Functional Components
Best practices for typing props, defaults, and composition in reusable SPFx components.
Props typing with TypeScript
Define explicit interfaces and prefer readonly props to communicate intent.
type CardProps = {
readonly title: string;
readonly subtitle?: string;
readonly onClick?: () => void;
readonly children?: React.ReactNode;
};
export function Card({ title, subtitle, onClick, children }: CardProps) {
return (
<div className="card" role={onClick ? 'button' : undefined} onClick={onClick}>
<h3>{title}</h3>
{subtitle && <p className="text-muted">{subtitle}</p>}
<div>{children}</div>
</div>
);
}
Default and optional props
Provide sensible defaults and avoid overloading components with too many props; split variants when needed.
type BadgeProps = { label: string; intent?: 'info' | 'success' | 'warn' | 'error' };
export function Badge({ label, intent = 'info' }: BadgeProps) {
return <span className={`badge badge-${intent}`}>{label}</span>;
}
Accessibility basics
Add roles/labels for interactive components, keyboard handlers, and focus management.
type IconButtonProps = { label: string; onClick: () => void };
export function IconButton({ label, onClick }: IconButtonProps) {
return (
<button aria-label={label} onClick={onClick}>
<svg aria-hidden="true">{/* icon */}</svg>
</button>
);
}
Reusability patterns
- Keep components small and focused.
- Use children-as-API for flexible content.
- Prefer composition over prop explosion.
Anti‑Patterns
- Components with large prop surfaces and conditional branches for many modes.
- Inline anonymous functions recreated every render without
useCallbackwhere performance matters. - Mixing data fetching directly into presentational components.
SPFx examples
Web part UI building blocks
type WebPartHeaderProps = { title: string; description?: string };
export function WebPartHeader({ title, description }: WebPartHeaderProps) {
return (
<header className="wp-header">
<h2>{title}</h2>
{description && <p>{description}</p>}
</header>
);
}
Memoize hot handlers
export function List({ items, onSelect }: { items: string[]; onSelect: (i: string) => void }) {
const handleClick = useCallback((i: string) => onSelect(i), [onSelect]);
return (
<ul>
{items.map(i => (
<li key={i}><button onClick={() => handleClick(i)}>{i}</button></li>
))}
</ul>
);
}