| name | frontend-component-patterns |
| description | Build reusable, composable, and maintainable React/Vue/Angular components following established design patterns like compound components, render props, custom hooks, and HOCs. Use when creating component libraries, implementing component composition, building reusable UI elements, designing prop APIs, managing component state patterns, implementing controlled vs uncontrolled components, creating compound components, using render props or children as functions, building custom hooks, or establishing component architecture standards. |
Frontend Component Patterns - Building Reusable React Components
When to use this skill
- Creating reusable component libraries
- Implementing component composition patterns
- Building flexible, configurable UI components
- Designing intuitive component prop APIs
- Managing component state with patterns
- Implementing controlled vs uncontrolled components
- Creating compound components (e.g., Tabs, Accordion)
- Using render props or children as functions
- Building custom React hooks for shared logic
- Implementing Higher-Order Components (HOCs)
- Establishing component architecture standards
- Creating accessible, keyboard-navigable components
When to use this skill
- Designing React component architecture, improving component reusability, managing state, or solving common UI patterns.
- When working on related tasks or features
- During development that requires this expertise
Use when: Designing React component architecture, improving component reusability, managing state, or solving common UI patterns.
Core Principles
- Composition Over Inheritance - Build complex UIs from simple components
- Single Responsibility - Each component does one thing well
- Props Down, Events Up - Unidirectional data flow
- Separation of Concerns - Logic separate from presentation
- Accessibility First - ARIA, keyboard navigation, semantic HTML
Component Patterns
1. Presentational vs Container Components
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <div>Loading...</div>;
return (
<div className="profile">
<img src={user.avatar} alt={user.name} />
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}
function UserProfileContainer() {
{ : user, isLoading } = (userId);
(isLoading) ;
(!user) ;
;
}
{
: {
: ;
: ;
: ;
};
}
() {
(
);
}
2. Compound Components
interface TabsProps {
children: React.ReactNode;
defaultValue?: string;
}
interface TabsContextValue {
activeTab: string;
setActiveTab: (value: string) => void;
}
const TabsContext = React.createContext<TabsContextValue | null>(null);
function Tabs({ children, defaultValue }: TabsProps) {
const [activeTab, setActiveTab] = useState(defaultValue || '');
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function TabsList({ children }: { children: React.ReactNode }) {
return < = =>{children};
}
() {
context = ();
(!context) ();
isActive = context. === value;
(
);
}
() {
context = ();
(!context) ();
(context. !== value) ;
;
}
{ , , , };
() {
(
);
}
3. Render Props Pattern
interface MousePositionProps {
children: (position: { x: number; y: number }) => React.ReactNode;
}
function MousePosition({ children }: MousePositionProps) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return <>{children(position)}</>;
}
function App() {
return (
<>
{({ x, y }) => (
Mouse is at ({x}, {y})
)}
);
}
4. Custom Hooks (Modern Alternative)
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return position;
}
function App() {
const { x, y } = useMousePosition();
return (
<div>
Mouse is at ({x}, {y})
</div>
);
}
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
( {
timer = ( (value), delay);
(timer);
}, [value, delay]);
debouncedValue;
}
useLocalStorage<T>(: , : T) {
[value, setValue] = useState<T>( {
stored = .(key);
stored ? .(stored) : initialValue;
});
( {
.(key, .(value));
}, [key, value]);
[value, setValue] ;
}
5. Higher-Order Components (Legacy Pattern)
function withLoading<P extends object>(
Component: React.ComponentType<P>
) {
return function WithLoadingComponent(
props: P & { isLoading: boolean }
) {
if (props.isLoading) {
return <LoadingSpinner />;
}
return <Component {...props} />;
};
}
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading users={users} isLoading={loading} />
State Management Patterns
1. Props vs State
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
function Button({ label, onClick, disabled }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function () {
(
);
}
() {
inputRef = useRef<>();
= () => {
.(inputRef.?.);
};
(
);
}
2. Lifting State Up
function ParentBad() {
return (
<>
<SearchBox /> {/* Has own search state */}
<ResultsList /> {/* Has own search state */}
</>
);
}
function ParentGood() {
const [searchQuery, setSearchQuery] = useState('');
return (
<>
<SearchBox query={searchQuery} onQueryChange={setSearchQuery} />
<ResultsList query={searchQuery} />
</>
);
}
3. Context for Deep Props
function App() {
const [theme, setTheme] = useState('light');
return <Layout theme={theme} setTheme={setTheme} />;
}
function Layout({ theme, setTheme }) {
return <Sidebar theme={theme} setTheme={setTheme} />;
}
function Sidebar({ theme, setTheme }) {
return <ThemeToggle theme={theme} setTheme={setTheme} />;
}
interface ThemeContextValue {
theme: string;
setTheme: (theme: string) => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
function ThemeProvider({ children }: { children: React.ReactNode }) {
[theme, setTheme] = ();
(
);
}
() {
context = ();
(!context) ();
context;
}
() {
{ theme, setTheme } = ();
(
);
}
Performance Optimization
1. React.memo - Prevent Re-renders
function ExpensiveComponent({ data }: { data: string }) {
console.log('Rendering...');
return <div>{data}</div>;
}
const ExpensiveComponent = memo(function ExpensiveComponent({
data
}: {
data: string
}) {
console.log('Rendering...');
return <div>{data}</div>;
});
const ExpensiveList = memo(
function ExpensiveList({ items }: { items: Item[] }) {
return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
},
(prevProps, nextProps) => {
return prevProps.. === nextProps..;
}
);
2. useMemo - Cache Expensive Calculations
function ProductList({ products, filters }: { products: Product[]; filters: Filters }) {
const filteredProducts = products.filter(p => matchesFilters(p, filters));
const filteredProducts = useMemo(() => {
return products.filter(p => matchesFilters(p, filters));
}, [products, filters]);
return (
<div>
{filteredProducts.map(p => (
<ProductCard key={p.id} product={p} />
))}
</div>
);
}
3. useCallback - Stable Function References
function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log('clicked');
};
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return (
<>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<MemoizedChild onClick={handleClick} />
</>
);
}
const MemoizedChild = memo(function Child({ onClick }: { onClick: () => void }) {
console.log('Child rendering');
return <button =>Click me;
});
4. Code Splitting & Lazy Loading
const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<HeavyChart />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
</Suspense>
);
}
Accessibility Patterns
1. Semantic HTML & ARIA
function AccessibleButton({ label, onClick }: { label: string; onClick: () => void }) {
return (
<button
type="button"
onClick={onClick}
aria-label={label}
>
{label}
</button>
);
}
function Modal({ isOpen, onClose, children }: {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
}) {
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
}
return () => {
document.body.style.overflow = '';
};
}, [isOpen]);
if (!isOpen) return null;
return (
<div
role="dialog"
aria-modal="true"
className=
=
>
e.stopPropagation()}>
{children}
×
);
}
() {
(
);
}
2. Keyboard Navigation
function Dropdown({ options }: { options: string[] }) {
const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
setIsOpen(!isOpen);
} else if (e.key === 'ArrowDown') {
setSelectedIndex((i) => Math.min(i + 1, options.length - 1));
} else if (e.key === 'ArrowUp') {
setSelectedIndex((i) => Math.max(i - 1, 0));
} else if (e.key === 'Escape') {
setIsOpen(false);
}
};
(
);
}
Component Design Checklist
Structure:
□ Single responsibility per component
□ Presentational vs container separation
□ Proper props typing (TypeScript)
□ Default props defined
□ Prop validation for critical inputs
State Management:
□ State at lowest necessary level
□ Lifted state when needed for sharing
□ Context for deep prop drilling
□ No prop mutations
□ Controlled components for forms
Performance:
□ memo() for expensive components
□ useMemo() for expensive calculations
□ useCallback() for stable callbacks
□ Code splitting for large components
□ Lazy loading for routes
Accessibility:
□ Semantic HTML elements
□ ARIA labels and roles
□ Keyboard navigation support
□ Focus management
□ Screen reader tested
Reusability:
□ Configurable via props
□ Composable with children
□ No hardcoded values
□ Clear, documented API
□ Example usage provided
Resources
Remember: Great components are simple, reusable, accessible, and performant. Start simple, add complexity only when needed.