# React Patterns

> When to activate: React 19, hooks, RSC, Server Components, Context, portals, error boundaries, performance optimization

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

---


# React Patterns

## Project Structure
```
src/
├── app/                  # Next.js App Router pages (or routes/)
├── components/
│   ├── ui/               # Headless / primitive UI atoms
│   ├── features/         # Feature-scoped components
│   └── layouts/          # Page shells
├── hooks/                # Custom hooks
├── lib/                  # Utilities, helpers
├── stores/               # Client state (Zustand / Jotai)
└── types/                # Shared TS types
```

## Hooks Patterns

### useCallback / useMemo
```tsx
// useMemo: expensive pure computation
const filtered = useMemo(
  () => items.filter(i => i.active && i.name.includes(query)),
  [items, query]
)

// useCallback: stable reference for child prop / effect dep
const handleSubmit = useCallback(async (data: FormData) => {
  await mutate(data)
}, [mutate])
```

### Custom Data Hook
```tsx
function useUsers(filter: string) {
  return useQuery({
    queryKey: ['users', filter],
    queryFn: () => api.users.list({ filter }),
    staleTime: 60_000,
  })
}
```

### Custom Event Hook
```tsx
function useKeyDown(key: string, handler: () => void) {
  useEffect(() => {
    const listener = (e: KeyboardEvent) => {
      if (e.key === key) handler()
    }
    window.addEventListener('keydown', listener)
    return () => window.removeEventListener('keydown', listener)
  }, [key, handler])
}
```

## Context
```tsx
const ThemeContext = createContext<Theme | null>(null)

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light')
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

export function useTheme() {
  const ctx = useContext(ThemeContext)
  if (!ctx) throw new Error('useTheme must be inside ThemeProvider')
  return ctx
}
```

## Compound Components
```tsx
interface TabsCtxValue { active: string; setActive: (id: string) => void }
const TabsCtx = createContext<TabsCtxValue | null>(null)

function Tabs({ defaultValue, children }: { defaultValue: string; children: ReactNode }) {
  const [active, setActive] = useState(defaultValue)
  return <TabsCtx.Provider value={{ active, setActive }}>{children}</TabsCtx.Provider>
}

function Tab({ value, children }: { value: string; children: ReactNode }) {
  const { active, setActive } = useContext(TabsCtx)!
  return (
    <button role="tab" aria-selected={active === value} onClick={() => setActive(value)}>
      {children}
    </button>
  )
}

function TabPanel({ value, children }: { value: string; children: ReactNode }) {
  const { active } = useContext(TabsCtx)!
  if (active !== value) return null
  return <div role="tabpanel">{children}</div>
}

Tabs.Tab = Tab
Tabs.Panel = TabPanel
```

## Error Boundaries
```tsx
class ErrorBoundary extends Component<
  { fallback: ReactNode; children: ReactNode },
  { error: Error | null }
> {
  state = { error: null }
  static getDerivedStateFromError(e: Error) { return { error: e } }
  componentDidCatch(e: Error, info: ErrorInfo) { console.error(e, info) }
  render() {
    return this.state.error ? this.props.fallback : this.props.children
  }
}
```

## Render Optimization

### React.memo with custom comparator
```tsx
const Row = memo(
  function Row({ item }: { item: Item }) {
    return <li>{item.name}</li>
  },
  (prev, next) => prev.item.id === next.item.id
)
```

### Virtualization for large lists
```tsx
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: () => 48,
  })

  return (
    <div ref={parentRef} style={{ height: 400, overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize() }}>
        {virtualizer.getVirtualItems().map(row => (
          <div key={row.key} style={{ transform: `translateY(${row.start}px)`, position: 'absolute', width: '100%' }}>
            {items[row.index].name}
          </div>
        ))}
      </div>
    </div>
  )
}
```

## React 19 Features

### use() for promises
```tsx
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise)  // suspends until resolved
  return <div>{user.name}</div>
}
```

### Server Actions
```tsx
'use server'
export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  await db.post.create({ data: { title } })
  revalidatePath('/posts')
}

// Component
<form action={createPost}>
  <input name="title" />
  <button type="submit">Create</button>
</form>
```

### useOptimistic
```tsx
function LikeButton({ postId, likes }: { postId: string; likes: number }) {
  const [optimisticLikes, addOptimistic] = useOptimistic(likes)
  async function handleLike() {
    addOptimistic(l => l + 1)
    await likePost(postId)
  }
  return <button onClick={handleLike}>{optimisticLikes} likes</button>
}
```

## Portals
```tsx
function Modal({ children, onClose }: { children: ReactNode; onClose: () => void }) {
  return createPortal(
    <div role="dialog" aria-modal>
      <button onClick={onClose}>Close</button>
      {children}
    </div>,
    document.getElementById('modal-root')!
  )
}
```

