# Frontend Development Patterns - Detailed Guide > This file contains the detailed procedure and reference material extracted from `SKILL.md` for focused loading. The root skill defines activation, examples, safety constraints, and limitations. ## Component Patterns ### Composition Over Inheritance ```typescript // ✅ GOOD: Component composition interface CardProps { children: React.ReactNode variant?: 'default' | 'outlined' } export function Card({ children, variant = 'default' }: CardProps) { return
{children}
} export function CardHeader({ children }: { children: React.ReactNode }) { return
{children}
} export function CardBody({ children }: { children: React.ReactNode }) { return
{children}
} // Usage Title Content ``` ### Compound Components ```typescript interface TabsContextValue { activeTab: string setActiveTab: (tab: string) => void } const TabsContext = createContext(undefined) export function Tabs({ children, defaultTab }: { children: React.ReactNode defaultTab: string }) { const [activeTab, setActiveTab] = useState(defaultTab) return ( {children} ) } export function TabList({ children }: { children: React.ReactNode }) { return
{children}
} 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 ( ) } // Usage Overview Details ``` ### Render Props Pattern ```typescript interface DataLoaderProps { url: string children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode } export function DataLoader({ url, children }: DataLoaderProps) { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { let active = true const controller = new AbortController() setLoading(true) setError(null) setData(null) fetch(url, { signal: controller.signal }) .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json() }) .then(value => { if (active) setData(value) }) .catch(err => { if (active) setError(err instanceof Error ? err : new Error('Load failed')) }) .finally(() => { if (active) setLoading(false) }) return () => { active = false; controller.abort() } }, [url]) return <>{children(data, loading, error)} } // Usage url="/api/markets"> {(markets, loading, error) => { if (loading) return if (error) return return }} ``` ## Custom Hooks Patterns ### State Management Hook ```typescript 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 ```typescript The hand-written query wrapper is omitted: an inline fetcher/options object in its usage recreated the callback on every render, retriggering requests indefinitely, and it lacked stale-result protection. Prefer a project-approved query library with stable query keys, cancellation, retry and cache policy. The DataLoader example above demonstrates scoped fetch cleanup only; it has no shared cache and must validate response data at runtime before treating it as T. ### Debounce Hook ```typescript export function useDebounce(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 const [searchQuery, setSearchQuery] = useState('') const debouncedQuery = useDebounce(searchQuery, 500) useEffect(() => { if (debouncedQuery) { performSearch(debouncedQuery) } }, [debouncedQuery]) ``` ## State Management Patterns ### Context + Reducer Pattern ```typescript 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 } | undefined>(undefined) export function MarketProvider({ children }: { children: React.ReactNode }) { const [state, dispatch] = useReducer(reducer, { markets: [], selectedMarket: null, loading: false }) return ( {children} ) } export function useMarkets() { const context = useContext(MarketContext) if (!context) throw new Error('useMarkets must be used within MarketProvider') return context } ``` ## Performance Optimization ### Memoization ```typescript // ✅ 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(({ market }) => { return (

{market.name}

{market.description}

) }) ``` ### Code Splitting & Lazy Loading ```typescript import { lazy, Suspense } from 'react' // ✅ Lazy load heavy components const HeavyChart = lazy(() => import('./HeavyChart')) const ThreeJsBackground = lazy(() => import('./ThreeJsBackground')) export function Dashboard() { return (
}>
) } ``` ### Virtualization for Long Lists ```typescript import { useVirtualizer } from '@tanstack/react-virtual' export function VirtualMarketList({ markets }: { markets: Market[] }) { const parentRef = useRef(null) const virtualizer = useVirtualizer({ count: markets.length, getScrollElement: () => parentRef.current, estimateSize: () => 100, // Estimated row height overscan: 5 // Extra items to render }) return (
{virtualizer.getVirtualItems().map(virtualRow => (
))}
) } ``` ## Form Handling Patterns ### Controlled Form with Validation ```typescript interface FormData { name: string description: string endDate: string } interface FormErrors { name?: string description?: string endDate?: string } export function CreateMarketForm() { const [formData, setFormData] = useState({ name: '', description: '', endDate: '' }) const [errors, setErrors] = useState({}) 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 (
setFormData(prev => ({ ...prev, name: e.target.value }))} placeholder="Market name" /> {errors.name && {errors.name}} {/* Other fields */}
) } ``` ## Error Boundary Pattern ```typescript 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) { // Send only sanitized diagnostics through the approved telemetry adapter. // Error messages/stacks may contain user data or credentials. } render() { if (this.state.hasError) { return (

Something went wrong

Please retry or contact support.

) } return this.props.children } } // Usage ``` ## Animation Patterns ### Framer Motion Animations ```typescript import { motion, AnimatePresence } from 'framer-motion' // ✅ List animations export function AnimatedMarketList({ markets }: { markets: Market[] }) { return ( {markets.map(market => ( ))} ) } // ✅ Modal animations export function Modal({ isOpen, onClose, children }: ModalProps) { return ( {isOpen && ( <> {children} )} ) } ``` ## Accessibility Patterns ### Keyboard Navigation ```typescript 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() if (!isOpen || options.length === 0) return onSelect(options[activeIndex]) setIsOpen(false) break case 'Escape': setIsOpen(false) break } } return (
{/* Dropdown implementation */}
) } ``` ### Focus Management ```typescript export function Modal({ isOpen, onClose, children }: ModalProps) { const modalRef = useRef(null) const previousFocusRef = useRef(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 ? (
e.key === 'Escape' && onClose()} > {children}
) : null } ``` **Remember**: Modern frontend patterns enable maintainable, performant user interfaces. Choose patterns that fit your project complexity. ## Integration and accessibility limits These independent TSX fragments require React imports, client-component boundaries in Next.js, application types and adapters; they are not a complete compiling app. Never treat a TypeScript generic as runtime response validation. Form validation must also run on the server with authorization and duplicate-submission protection; expose a visible error state rather than swallow failures. The Tab, Dropdown and animated Modal snippets demonstrate composition/animation only, not accessible production widgets. Tabs need linked tab/panel IDs and keyboard focus behavior; a combobox needs an actual focusable input and associated options; modal dialogs need an accessible name, focus trapping, inert background and restoration on unmount. Use a tested accessible primitive or native select/dialog and test keyboard/screen-reader behavior. Respect reduced motion. Error boundaries do not catch event-handler or asynchronous failures.