frontend-patterns

Frontend Development Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "frontend-patterns" with this command: npx skills add fimoklei/pm-ai-playbook/fimoklei-pm-ai-playbook-frontend-patterns

Frontend Development Patterns

Modern frontend patterns for React, Next.js, and performant user interfaces.

Component Patterns

Composition Over Inheritance

// ✅ GOOD: Component composition interface CardProps { children: React.ReactNode variant?: 'default' | 'outlined' }

export function Card({ children, variant = 'default' }: CardProps) { return <div className={card card-${variant}}>{children}</div> }

export function CardHeader({ children }: { children: React.ReactNode }) { return <div className="card-header">{children}</div> }

export function CardBody({ children }: { children: React.ReactNode }) { return <div className="card-body">{children}</div> }

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

Compound Components

interface TabsContextValue { activeTab: string setActiveTab: (tab: string) => void }

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

export function Tabs({ children, defaultTab }: { children: React.ReactNode defaultTab: string }) { const [activeTab, setActiveTab] = useState(defaultTab)

return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> {children} </TabsContext.Provider> ) }

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

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

return ( <button className={context.activeTab === id ? 'active' : ''} onClick={() => context.setActiveTab(id)} > {children} </button> ) }

// Usage <Tabs defaultTab="overview"> <TabList> <Tab id="overview">Overview</Tab> <Tab id="details">Details</Tab> </TabList> </Tabs>

Render Props Pattern

interface DataLoaderProps<T> { url: string children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode }

export function DataLoader<T>({ url, children }: DataLoaderProps<T>) { 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 <>{children(data, loading, error)}</> }

// Usage <DataLoader<Market[]> url="/api/markets"> {(markets, loading, error) => { if (loading) return <Spinner /> if (error) return <Error error={error} /> return <MarketList markets={markets!} /> }} </DataLoader>

Custom Hooks Patterns

State Management Hook

export function useToggle(initialValue = false): [boolean, () => void] { const [value, setValue] = useState(initialValue)

const toggle = useCallback(() => { setValue(v => !v) }, [])

return [value, toggle] }

// Usage const [isOpen, toggleOpen] = useToggle()

Async Data Fetching Hook

interface UseQueryOptions<T> { onSuccess?: (data: T) => void onError?: (error: Error) => void enabled?: boolean }

export function useQuery<T>( key: string, fetcher: () => Promise<T>, options?: UseQueryOptions<T> ) { const [data, setData] = useState<T | null>(null) const [error, setError] = useState<Error | null>(null) const [loading, setLoading] = useState(false)

const refetch = useCallback(async () => { setLoading(true) setError(null)

try {
  const result = await fetcher()
  setData(result)
  options?.onSuccess?.(result)
} catch (err) {
  const error = err as Error
  setError(error)
  options?.onError?.(error)
} finally {
  setLoading(false)
}

}, [fetcher, options])

useEffect(() => { if (options?.enabled !== false) { refetch() } }, [key, refetch, options?.enabled])

return { data, error, loading, refetch } }

// Usage const { data: markets, loading, error, refetch } = useQuery( 'markets', () => fetch('/api/markets').then(r => r.json()), { onSuccess: data => console.log('Fetched', data.length, 'markets'), onError: err => console.error('Failed:', err) } )

Debounce Hook

export function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState<T>(value)

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

return () => clearTimeout(handler)

}, [value, delay])

return debouncedValue }

// Usage const [searchQuery, setSearchQuery] = useState('') const debouncedQuery = useDebounce(searchQuery, 500)

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

State Management Patterns

Context + Reducer Pattern

interface State { markets: Market[] selectedMarket: Market | null loading: boolean }

type Action = | { type: 'SET_MARKETS'; payload: Market[] } | { type: 'SELECT_MARKET'; payload: Market } | { type: 'SET_LOADING'; payload: boolean }

function reducer(state: State, action: Action): State { switch (action.type) { case 'SET_MARKETS': return { ...state, markets: action.payload } case 'SELECT_MARKET': return { ...state, selectedMarket: action.payload } case 'SET_LOADING': return { ...state, loading: action.payload } default: return state } }

const MarketContext = createContext<{ state: State dispatch: Dispatch<Action> } | undefined>(undefined)

export function MarketProvider({ children }: { children: React.ReactNode }) { const [state, dispatch] = useReducer(reducer, { markets: [], selectedMarket: null, loading: false })

return ( <MarketContext.Provider value={{ state, dispatch }}> {children} </MarketContext.Provider> ) }

export function useMarkets() { const context = useContext(MarketContext) if (!context) throw new Error('useMarkets must be used within MarketProvider') return context }

Performance Optimization

Memoization

// ✅ useMemo for expensive computations const sortedMarkets = useMemo(() => { return markets.sort((a, b) => b.volume - a.volume) }, [markets])

// ✅ useCallback for functions passed to children const handleSearch = useCallback((query: string) => { setSearchQuery(query) }, [])

// ✅ React.memo for pure components export const MarketCard = React.memo<MarketCardProps>(({ market }) => { return ( <div className="market-card"> <h3>{market.name}</h3> <p>{market.description}</p> </div> ) })

Code Splitting & Lazy Loading

import { lazy, Suspense } from 'react'

// ✅ Lazy load heavy components const HeavyChart = lazy(() => import('./HeavyChart')) const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))

export function Dashboard() { return ( <div> <Suspense fallback={<ChartSkeleton />}> <HeavyChart data={data} /> </Suspense>

  &#x3C;Suspense fallback={null}>
    &#x3C;ThreeJsBackground />
  &#x3C;/Suspense>
&#x3C;/div>

) }

Virtualization for Long Lists

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

export function VirtualMarketList({ markets }: { markets: Market[] }) { const parentRef = useRef<HTMLDivElement>(null)

const virtualizer = useVirtualizer({ count: markets.length, getScrollElement: () => parentRef.current, estimateSize: () => 100, // Estimated row height overscan: 5 // Extra items to render })

return ( <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}> <div style={{ height: ${virtualizer.getTotalSize()}px, position: 'relative' }} > {virtualizer.getVirtualItems().map(virtualRow => ( <div key={virtualRow.index} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: ${virtualRow.size}px, transform: translateY(${virtualRow.start}px) }} > <MarketCard market={markets[virtualRow.index]} /> </div> ))} </div> </div> ) }

Form Handling Patterns

Controlled Form with Validation

interface FormData { name: string description: string endDate: string }

interface FormErrors { name?: string description?: string endDate?: string }

export function CreateMarketForm() { const [formData, setFormData] = useState<FormData>({ name: '', description: '', endDate: '' })

const [errors, setErrors] = useState<FormErrors>({})

const validate = (): boolean => { const newErrors: FormErrors = {}

if (!formData.name.trim()) {
  newErrors.name = 'Name is required'
} else if (formData.name.length > 200) {
  newErrors.name = 'Name must be under 200 characters'
}

if (!formData.description.trim()) {
  newErrors.description = 'Description is required'
}

if (!formData.endDate) {
  newErrors.endDate = 'End date is required'
}

setErrors(newErrors)
return Object.keys(newErrors).length === 0

}

const handleSubmit = async (e: React.FormEvent) => { e.preventDefault()

if (!validate()) return

try {
  await createMarket(formData)
  // Success handling
} catch (error) {
  // Error handling
}

}

return ( <form onSubmit={handleSubmit}> <input value={formData.name} onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))} placeholder="Market name" /> {errors.name && <span className="error">{errors.name}</span>}

  {/* Other fields */}

  &#x3C;button type="submit">Create Market&#x3C;/button>
&#x3C;/form>

) }

Error Boundary Pattern

interface ErrorBoundaryState { hasError: boolean error: Error | null }

export class ErrorBoundary extends React.Component< { children: React.ReactNode }, ErrorBoundaryState

{ state: ErrorBoundaryState = { hasError: false, error: null }

static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error } }

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error('Error boundary caught:', error, errorInfo) }

render() { if (this.state.hasError) { return ( <div className="error-fallback"> <h2>Something went wrong</h2> <p>{this.state.error?.message}</p> <button onClick={() => this.setState({ hasError: false })}> Try again </button> </div> ) }

return this.props.children

} }

// Usage <ErrorBoundary> <App /> </ErrorBoundary>

Animation Patterns

Framer Motion Animations

import { motion, AnimatePresence } from 'framer-motion'

// ✅ List animations export function AnimatedMarketList({ markets }: { markets: Market[] }) { return ( <AnimatePresence> {markets.map(market => ( <motion.div key={market.id} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.3 }} > <MarketCard market={market} /> </motion.div> ))} </AnimatePresence> ) }

// ✅ Modal animations export function Modal({ isOpen, onClose, children }: ModalProps) { return ( <AnimatePresence> {isOpen && ( <> <motion.div className="modal-overlay" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} onClick={onClose} /> <motion.div className="modal-content" initial={{ opacity: 0, scale: 0.9, y: 20 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9, y: 20 }} > {children} </motion.div> </> )} </AnimatePresence> ) }

Accessibility Patterns

Keyboard Navigation

export function Dropdown({ options, onSelect }: DropdownProps) { const [isOpen, setIsOpen] = useState(false) const [activeIndex, setActiveIndex] = useState(0)

const handleKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault() setActiveIndex(i => Math.min(i + 1, options.length - 1)) break case 'ArrowUp': e.preventDefault() setActiveIndex(i => Math.max(i - 1, 0)) break case 'Enter': e.preventDefault() onSelect(options[activeIndex]) setIsOpen(false) break case 'Escape': setIsOpen(false) break } }

return ( <div role="combobox" aria-expanded={isOpen} aria-haspopup="listbox" onKeyDown={handleKeyDown} > {/* Dropdown implementation */} </div> ) }

Focus Management

export function Modal({ isOpen, onClose, children }: ModalProps) { const modalRef = useRef<HTMLDivElement>(null) const previousFocusRef = useRef<HTMLElement | null>(null)

useEffect(() => { if (isOpen) { // Save currently focused element previousFocusRef.current = document.activeElement as HTMLElement

  // Focus modal
  modalRef.current?.focus()
} else {
  // Restore focus when closing
  previousFocusRef.current?.focus()
}

}, [isOpen])

return isOpen ? ( <div ref={modalRef} role="dialog" aria-modal="true" tabIndex={-1} onKeyDown={e => e.key === 'Escape' && onClose()} > {children} </div> ) : null }

Remember: Modern frontend patterns enable maintainable, performant user interfaces. Choose patterns that fit your project complexity.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

first-principles-decomposer

No summary provided by upstream source.

Repository SourceNeeds Review
General

pre-mortem-analyst

No summary provided by upstream source.

Repository SourceNeeds Review
General

simplification-cascades

No summary provided by upstream source.

Repository SourceNeeds Review