# Component Patterns

> Implement React component patterns including composition, custom hooks, render props, HOCs, and compound components. Use when building reusable React components, implementing design patterns, or refactoring component architecture.

- Skill: `armanzeroeight/component-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add armanzeroeight/component-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/armanzeroeight/component-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: armanzeroeight (https://skillmd.com/u/armanzeroeight)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/armanzeroeight/component-patterns

---


# Component Patterns

Implement React component patterns for building reusable, maintainable components.

## Quick Start

Use composition for most cases, custom hooks for shared logic, and compound components for flexible APIs.

## Instructions

### Composition Pattern

The default pattern for building flexible components.

**Basic composition:**
```tsx
function Card({ children }) {
  return <div className="card">{children}</div>;
}

function CardHeader({ children }) {
  return <div className="card-header">{children}</div>;
}

function CardBody({ children }) {
  return <div className="card-body">{children}</div>;
}

// Usage
<Card>
  <CardHeader>Title</CardHeader>
  <CardBody>Content</CardBody>
</Card>
```

**With props:**
```tsx
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
}

function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
  return (
    <button className={`btn btn-${variant} btn-${size}`}>
      {children}
    </button>
  );
}
```

### Custom Hooks Pattern

Extract and reuse stateful logic across components.

**Basic custom hook:**
```tsx
function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  
  const toggle = useCallback(() => {
    setValue(v => !v);
  }, []);
  
  return [value, toggle] as const;
}

// Usage
function Component() {
  const [isOpen, toggleOpen] = useToggle();
  return <button onClick={toggleOpen}>{isOpen ? 'Close' : 'Open'}</button>;
}
```

**Data fetching hook:**
```tsx
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);
  
  return { data, loading, error };
}
```

**Form hook:**
```tsx
function useForm<T>(initialValues: T) {
  const [values, setValues] = useState(initialValues);
  
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValues(prev => ({
      ...prev,
      [e.target.name]: e.target.value
    }));
  };
  
  const reset = () => setValues(initialValues);
  
  return { values, handleChange, reset };
}
```

### Compound Components Pattern

Create flexible component APIs with implicit state sharing.

**Basic compound component:**
```tsx
interface TabsContextValue {
  activeTab: string;
  setActiveTab: (tab: string) => void;
}

const TabsContext = createContext<TabsContextValue | null>(null);

function Tabs({ children, defaultTab }: { children: React.ReactNode; defaultTab: string }) {
  const [activeTab, setActiveTab] = useState(defaultTab);
  
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }: { children: React.ReactNode }) {
  return <div className="tab-list">{children}</div>;
}

function Tab({ id, children }: { id: string; children: React.ReactNode }) {
  const context = useContext(TabsContext);
  if (!context) throw new Error('Tab must be used within Tabs');
  
  const { activeTab, setActiveTab } = context;
  
  return (
    <button
      className={activeTab === id ? 'active' : ''}
      onClick={() => setActiveTab(id)}
    >
      {children}
    </button>
  );
}

function TabPanel({ id, children }: { id: string; children: React.ReactNode }) {
  const context = useContext(TabsContext);
  if (!context) throw new Error('TabPanel must be used within Tabs');
  
  if (context.activeTab !== id) return null;
  return <div className="tab-panel">{children}</div>;
}

// Attach sub-components
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

// Usage
<Tabs defaultTab="home">
  <Tabs.List>
    <Tabs.Tab id="home">Home</Tabs.Tab>
    <Tabs.Tab id="profile">Profile</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel id="home">Home content</Tabs.Panel>
  <Tabs.Panel id="profile">Profile content</Tabs.Panel>
</Tabs>
```

### Render Props Pattern

Provide render flexibility through function props.

**Basic render prop:**
```tsx
interface MouseTrackerProps {
  render: (position: { x: number; y: number }) => React.ReactNode;
}

function MouseTracker({ render }: MouseTrackerProps) {
  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 <>{render(position)}</>;
}

// Usage
<MouseTracker
  render={({ x, y }) => (
    <div>Mouse at {x}, {y}</div>
  )}
/>
```

**Children as function:**
```tsx
interface DataProviderProps<T> {
  url: string;
  children: (data: T | null, loading: boolean) => React.ReactNode;
}

function DataProvider<T>({ url, children }: DataProviderProps<T>) {
  const { data, loading } = useFetch<T>(url);
  return <>{children(data, loading)}</>;
}

// Usage
<DataProvider url="/api/users">
  {(users, loading) => (
    loading ? <Spinner /> : <UserList users={users} />
  )}
</DataProvider>
```

### Higher-Order Component (HOC) Pattern

Wrap components to add functionality (legacy pattern, prefer hooks).

**Basic HOC:**
```tsx
function withLoading<P extends object>(
  Component: React.ComponentType<P>
) {
  return function WithLoadingComponent(props: P & { loading: boolean }) {
    const { loading, ...rest } = props;
    
    if (loading) return <Spinner />;
    return <Component {...(rest as P)} />;
  };
}

// Usage
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading users={users} loading={loading} />
```

**HOC with additional props:**
```tsx
function withAuth<P extends object>(
  Component: React.ComponentType<P & { user: User }>
) {
  return function WithAuthComponent(props: P) {
    const { user, loading } = useAuth();
    
    if (loading) return <Spinner />;
    if (!user) return <Navigate to="/login" />;
    
    return <Component {...props} user={user} />;
  };
}
```

## Performance Optimization Patterns

### React.memo

Prevent unnecessary re-renders of expensive components.

```tsx
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  // Expensive rendering logic
  return <div>{/* Complex UI */}</div>;
});

// With custom comparison
const MemoizedComponent = React.memo(
  function Component({ user }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
```

### useMemo

Memoize expensive calculations.

```tsx
function Component({ items }) {
  const sortedItems = useMemo(() => {
    return items.sort((a, b) => a.value - b.value);
  }, [items]);
  
  const total = useMemo(() => {
    return items.reduce((sum, item) => sum + item.price, 0);
  }, [items]);
  
  return <div>{/* Use sortedItems and total */}</div>;
}
```

### useCallback

Memoize functions to prevent child re-renders.

```tsx
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);
  
  return <MemoizedChild onClick={handleClick} />;
}

const MemoizedChild = React.memo(function Child({ onClick }) {
  return <button onClick={onClick}>Click</button>;
});
```

### Code Splitting

Split code to reduce initial bundle size.

```tsx
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <LazyComponent />
    </Suspense>
  );
}

// Route-based splitting
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));

<Routes>
  <Route path="/dashboard" element={
    <Suspense fallback={<Spinner />}>
      <Dashboard />
    </Suspense>
  } />
</Routes>
```

### Virtual Scrolling

Handle large lists efficiently.

```tsx
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });
  
  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map(virtualItem => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}
```

## Common Patterns

### Container/Presentational Pattern

Separate logic from presentation.

```tsx
// Presentational component
interface UserListProps {
  users: User[];
  onDelete: (id: string) => void;
}

function UserList({ users, onDelete }: UserListProps) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.name}
          <button onClick={() => onDelete(user.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

// Container component
function UserListContainer() {
  const { data: users, isLoading } = useQuery(['users'], fetchUsers);
  const deleteMutation = useMutation(deleteUser);
  
  if (isLoading) return <Spinner />;
  
  return <UserList users={users} onDelete={deleteMutation.mutate} />;
}
```

### Provider Pattern

Share data across component tree.

```tsx
interface ThemeContextValue {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  
  const toggleTheme = useCallback(() => {
    setTheme(t => t === 'light' ? 'dark' : 'light');
  }, []);
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) throw new Error('useTheme must be used within ThemeProvider');
  return context;
}
```

### Controlled vs Uncontrolled Components

**Controlled (recommended):**
```tsx
function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}
```

**Uncontrolled:**
```tsx
function UncontrolledInput() {
  const inputRef = useRef<HTMLInputElement>(null);
  
  const handleSubmit = () => {
    console.log(inputRef.current?.value);
  };
  
  return <input ref={inputRef} />;
}
```

## Troubleshooting

**Unnecessary re-renders:**
- Use React DevTools Profiler to identify
- Wrap with React.memo
- Use useMemo/useCallback appropriately
- Check if state is lifted too high

**Props drilling:**
- Use Context API for deeply nested props
- Consider component composition
- Extract intermediate components

**Stale closures in hooks:**
- Add dependencies to useEffect/useCallback
- Use functional updates: `setState(prev => prev + 1)`
- Use useRef for mutable values

**Memory leaks:**
- Clean up subscriptions in useEffect
- Cancel pending requests on unmount
- Remove event listeners

