Claude
Skills
Sign in
Back

frontend-patterns

Included with Lifetime
$97 forever

React component patterns, state management, accessibility, and frontend best practices. Use when building UI components or implementing client-side features.

Design

What this skill does


# Frontend Patterns

Comprehensive patterns for React components, state management, accessibility, and modern frontend development.

## When to Use

- Building React components
- Managing client state
- Implementing user interactions
- Handling forms and validation
- Optimizing performance
- Ensuring accessibility

## Component Patterns

### Component Structure

```typescript
// ✅ CORRECT: Well-structured component
'use client'

import { useState, useEffect } from 'react'
import { Button } from '@/components/ui/Button'
import { LoadingSpinner } from '@/components/ui/LoadingSpinner'

interface MarketCardProps {
  id: string
  name: string
  description: string
  endDate: Date
  onBet?: (marketId: string, outcome: string) => void
}

export function MarketCard({
  id,
  name,
  description,
  endDate,
  onBet
}: MarketCardProps) {
  const [isLoading, setIsLoading] = useState(false)

  const handleBet = async (outcome: string) => {
    setIsLoading(true)
    try {
      await onBet?.(id, outcome)
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div className="border rounded-lg p-4" data-testid="market-card">
      <h3 className="text-xl font-bold">{name}</h3>
      <p className="text-gray-600">{description}</p>
      <div className="mt-4 flex gap-2">
        <Button
          onClick={() => handleBet('yes')}
          disabled={isLoading}
          aria-label={`Bet yes on ${name}`}
        >
          {isLoading ? <LoadingSpinner /> : 'Yes'}
        </Button>
        <Button
          onClick={() => handleBet('no')}
          disabled={isLoading}
          aria-label={`Bet no on ${name}`}
        >
          {isLoading ? <LoadingSpinner /> : 'No'}
        </Button>
      </div>
    </div>
  )
}
```

### Compound Component Pattern

```typescript
// Flexible, composable components
interface TabsContextValue {
  activeTab: string
  setActiveTab: (tab: string) => void
}

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

export 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>
  )
}

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

export function Tab({ value, children }: {
  value: string
  children: React.ReactNode
}) {
  const context = useContext(TabsContext)
  if (!context) throw new Error('Tab must be used within Tabs')

  const { activeTab, setActiveTab } = context
  const isActive = activeTab === value

  return (
    <button
      role="tab"
      aria-selected={isActive}
      className={isActive ? 'tab-active' : 'tab'}
      onClick={() => setActiveTab(value)}
    >
      {children}
    </button>
  )
}

export function TabPanel({ value, children }: {
  value: string
  children: React.ReactNode
}) {
  const context = useContext(TabsContext)
  if (!context) throw new Error('TabPanel must be used within Tabs')

  if (context.activeTab !== value) return null

  return <div role="tabpanel">{children}</div>
}

// Usage
<Tabs defaultTab="markets">
  <TabList>
    <Tab value="markets">Markets</Tab>
    <Tab value="portfolio">Portfolio</Tab>
    <Tab value="history">History</Tab>
  </TabList>

  <TabPanel value="markets">
    <MarketsList />
  </TabPanel>
  <TabPanel value="portfolio">
    <Portfolio />
  </TabPanel>
  <TabPanel value="history">
    <TransactionHistory />
  </TabPanel>
</Tabs>
```

### Custom Hooks Pattern

```typescript
// Reusable logic extraction
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value)

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)

    return () => {
      clearTimeout(handler)
    }
  }, [value, delay])

  return debouncedValue
}

// Usage
function SearchMarkets() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 500)

  useEffect(() => {
    if (debouncedQuery) {
      searchMarkets(debouncedQuery)
    }
  }, [debouncedQuery])

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search markets"
    />
  )
}
```

### Async State Management

```typescript
// useAsync hook for data fetching
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error }

export function useAsync<T>(asyncFn: () => Promise<T>, deps: any[] = []) {
  const [state, setState] = useState<AsyncState<T>>({ status: 'idle' })

  useEffect(() => {
    let cancelled = false

    setState({ status: 'loading' })

    asyncFn()
      .then(data => {
        if (!cancelled) {
          setState({ status: 'success', data })
        }
      })
      .catch(error => {
        if (!cancelled) {
          setState({ status: 'error', error })
        }
      })

    return () => {
      cancelled = true
    }
  }, deps)

  return state
}

// Usage
function MarketDetails({ id }: { id: string }) {
  const state = useAsync(() => fetchMarket(id), [id])

  if (state.status === 'loading') {
    return <LoadingSpinner />
  }

  if (state.status === 'error') {
    return <ErrorMessage error={state.error.message} />
  }

  if (state.status === 'success') {
    return <MarketCard {...state.data} />
  }

  return null
}
```

## State Management Patterns

### React Context for Global State

```typescript
// Context setup
interface AppContextValue {
  user: User | null
  setUser: (user: User | null) => void
  theme: 'light' | 'dark'
  toggleTheme: () => void
}

const AppContext = createContext<AppContextValue | null>(null)

export function AppProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const [theme, setTheme] = useState<'light' | 'dark'>('light')

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light')
  }

  useEffect(() => {
    // Persist theme
    localStorage.setItem('theme', theme)
    document.documentElement.classList.toggle('dark', theme === 'dark')
  }, [theme])

  return (
    <AppContext.Provider value={{ user, setUser, theme, toggleTheme }}>
      {children}
    </AppContext.Provider>
  )
}

export function useApp() {
  const context = useContext(AppContext)
  if (!context) {
    throw new Error('useApp must be used within AppProvider')
  }
  return context
}
```

### Zustand for Complex State

```typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface CartStore {
  items: CartItem[]
  addItem: (item: CartItem) => void
  removeItem: (id: string) => void
  clearCart: () => void
  total: number
}

export const useCartStore = create<CartStore>()(
  persist(
    (set, get) => ({
      items: [],

      addItem: (item) => set((state) => ({
        items: [...state.items, item]
      })),

      removeItem: (id) => set((state) => ({
        items: state.items.filter(item => item.id !== id)
      })),

      clearCart: () => set({ items: [] }),

      get total() {
        return get().items.reduce((sum, item) => sum + item.price, 0)
      }
    }),
    {
      name: 'cart-storage'
    }
  )
)

// Usage
function Cart() {
  const { items, removeItem, total } = useCartStore()

  return (
    <div>
      <h2>Cart Total: ${total}</h2>
      {items.map(item => (
        <div key={item.id}>
          <span>{item.name}</span>
          <button onClick={() => removeItem(item.id)}>Remove</button>
        </div>
      ))}
    </div>
  )
}
```

### React Query for Server State

```typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

// Fetch markets
export function useMarkets() {
  return useQuery({
    queryKey: ['markets'],
    que

Related in Design