Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.
Add this skill
npx mdskills install sickn33/cc-skill-frontend-patternsComprehensive React patterns library with strong examples but lacks agent trigger conditions
1---2name: frontend-patterns3description: Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.4author: affaan-m5version: "1.0"6---78# Frontend Development Patterns910Modern frontend patterns for React, Next.js, and performant user interfaces.1112## Component Patterns1314### Composition Over Inheritance1516```typescript17// ✅ GOOD: Component composition18interface CardProps {19 children: React.ReactNode20 variant?: 'default' | 'outlined'21}2223export function Card({ children, variant = 'default' }: CardProps) {24 return <div className={`card card-${variant}`}>{children}</div>25}2627export function CardHeader({ children }: { children: React.ReactNode }) {28 return <div className="card-header">{children}</div>29}3031export function CardBody({ children }: { children: React.ReactNode }) {32 return <div className="card-body">{children}</div>33}3435// Usage36<Card>37 <CardHeader>Title</CardHeader>38 <CardBody>Content</CardBody>39</Card>40```4142### Compound Components4344```typescript45interface TabsContextValue {46 activeTab: string47 setActiveTab: (tab: string) => void48}4950const TabsContext = createContext<TabsContextValue | undefined>(undefined)5152export function Tabs({ children, defaultTab }: {53 children: React.ReactNode54 defaultTab: string55}) {56 const [activeTab, setActiveTab] = useState(defaultTab)5758 return (59 <TabsContext.Provider value={{ activeTab, setActiveTab }}>60 {children}61 </TabsContext.Provider>62 )63}6465export function TabList({ children }: { children: React.ReactNode }) {66 return <div className="tab-list">{children}</div>67}6869export function Tab({ id, children }: { id: string, children: React.ReactNode }) {70 const context = useContext(TabsContext)71 if (!context) throw new Error('Tab must be used within Tabs')7273 return (74 <button75 className={context.activeTab === id ? 'active' : ''}76 onClick={() => context.setActiveTab(id)}77 >78 {children}79 </button>80 )81}8283// Usage84<Tabs defaultTab="overview">85 <TabList>86 <Tab id="overview">Overview</Tab>87 <Tab id="details">Details</Tab>88 </TabList>89</Tabs>90```9192### Render Props Pattern9394```typescript95interface DataLoaderProps<T> {96 url: string97 children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode98}99100export function DataLoader<T>({ url, children }: DataLoaderProps<T>) {101 const [data, setData] = useState<T | null>(null)102 const [loading, setLoading] = useState(true)103 const [error, setError] = useState<Error | null>(null)104105 useEffect(() => {106 fetch(url)107 .then(res => res.json())108 .then(setData)109 .catch(setError)110 .finally(() => setLoading(false))111 }, [url])112113 return <>{children(data, loading, error)}</>114}115116// Usage117<DataLoader<Market[]> url="/api/markets">118 {(markets, loading, error) => {119 if (loading) return <Spinner />120 if (error) return <Error error={error} />121 return <MarketList markets={markets!} />122 }}123</DataLoader>124```125126## Custom Hooks Patterns127128### State Management Hook129130```typescript131export function useToggle(initialValue = false): [boolean, () => void] {132 const [value, setValue] = useState(initialValue)133134 const toggle = useCallback(() => {135 setValue(v => !v)136 }, [])137138 return [value, toggle]139}140141// Usage142const [isOpen, toggleOpen] = useToggle()143```144145### Async Data Fetching Hook146147```typescript148interface UseQueryOptions<T> {149 onSuccess?: (data: T) => void150 onError?: (error: Error) => void151 enabled?: boolean152}153154export function useQuery<T>(155 key: string,156 fetcher: () => Promise<T>,157 options?: UseQueryOptions<T>158) {159 const [data, setData] = useState<T | null>(null)160 const [error, setError] = useState<Error | null>(null)161 const [loading, setLoading] = useState(false)162163 const refetch = useCallback(async () => {164 setLoading(true)165 setError(null)166167 try {168 const result = await fetcher()169 setData(result)170 options?.onSuccess?.(result)171 } catch (err) {172 const error = err as Error173 setError(error)174 options?.onError?.(error)175 } finally {176 setLoading(false)177 }178 }, [fetcher, options])179180 useEffect(() => {181 if (options?.enabled !== false) {182 refetch()183 }184 }, [key, refetch, options?.enabled])185186 return { data, error, loading, refetch }187}188189// Usage190const { data: markets, loading, error, refetch } = useQuery(191 'markets',192 () => fetch('/api/markets').then(r => r.json()),193 {194 onSuccess: data => console.log('Fetched', data.length, 'markets'),195 onError: err => console.error('Failed:', err)196 }197)198```199200### Debounce Hook201202```typescript203export function useDebounce<T>(value: T, delay: number): T {204 const [debouncedValue, setDebouncedValue] = useState<T>(value)205206 useEffect(() => {207 const handler = setTimeout(() => {208 setDebouncedValue(value)209 }, delay)210211 return () => clearTimeout(handler)212 }, [value, delay])213214 return debouncedValue215}216217// Usage218const [searchQuery, setSearchQuery] = useState('')219const debouncedQuery = useDebounce(searchQuery, 500)220221useEffect(() => {222 if (debouncedQuery) {223 performSearch(debouncedQuery)224 }225}, [debouncedQuery])226```227228## State Management Patterns229230### Context + Reducer Pattern231232```typescript233interface State {234 markets: Market[]235 selectedMarket: Market | null236 loading: boolean237}238239type Action =240 | { type: 'SET_MARKETS'; payload: Market[] }241 | { type: 'SELECT_MARKET'; payload: Market }242 | { type: 'SET_LOADING'; payload: boolean }243244function reducer(state: State, action: Action): State {245 switch (action.type) {246 case 'SET_MARKETS':247 return { ...state, markets: action.payload }248 case 'SELECT_MARKET':249 return { ...state, selectedMarket: action.payload }250 case 'SET_LOADING':251 return { ...state, loading: action.payload }252 default:253 return state254 }255}256257const MarketContext = createContext<{258 state: State259 dispatch: Dispatch<Action>260} | undefined>(undefined)261262export function MarketProvider({ children }: { children: React.ReactNode }) {263 const [state, dispatch] = useReducer(reducer, {264 markets: [],265 selectedMarket: null,266 loading: false267 })268269 return (270 <MarketContext.Provider value={{ state, dispatch }}>271 {children}272 </MarketContext.Provider>273 )274}275276export function useMarkets() {277 const context = useContext(MarketContext)278 if (!context) throw new Error('useMarkets must be used within MarketProvider')279 return context280}281```282283## Performance Optimization284285### Memoization286287```typescript288// ✅ useMemo for expensive computations289const sortedMarkets = useMemo(() => {290 return markets.sort((a, b) => b.volume - a.volume)291}, [markets])292293// ✅ useCallback for functions passed to children294const handleSearch = useCallback((query: string) => {295 setSearchQuery(query)296}, [])297298// ✅ React.memo for pure components299export const MarketCard = React.memo<MarketCardProps>(({ market }) => {300 return (301 <div className="market-card">302 <h3>{market.name}</h3>303 <p>{market.description}</p>304 </div>305 )306})307```308309### Code Splitting & Lazy Loading310311```typescript312import { lazy, Suspense } from 'react'313314// ✅ Lazy load heavy components315const HeavyChart = lazy(() => import('./HeavyChart'))316const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))317318export function Dashboard() {319 return (320 <div>321 <Suspense fallback={<ChartSkeleton />}>322 <HeavyChart data={data} />323 </Suspense>324325 <Suspense fallback={null}>326 <ThreeJsBackground />327 </Suspense>328 </div>329 )330}331```332333### Virtualization for Long Lists334335```typescript336import { useVirtualizer } from '@tanstack/react-virtual'337338export function VirtualMarketList({ markets }: { markets: Market[] }) {339 const parentRef = useRef<HTMLDivElement>(null)340341 const virtualizer = useVirtualizer({342 count: markets.length,343 getScrollElement: () => parentRef.current,344 estimateSize: () => 100, // Estimated row height345 overscan: 5 // Extra items to render346 })347348 return (349 <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>350 <div351 style={{352 height: `${virtualizer.getTotalSize()}px`,353 position: 'relative'354 }}355 >356 {virtualizer.getVirtualItems().map(virtualRow => (357 <div358 key={virtualRow.index}359 style={{360 position: 'absolute',361 top: 0,362 left: 0,363 width: '100%',364 height: `${virtualRow.size}px`,365 transform: `translateY(${virtualRow.start}px)`366 }}367 >368 <MarketCard market={markets[virtualRow.index]} />369 </div>370 ))}371 </div>372 </div>373 )374}375```376377## Form Handling Patterns378379### Controlled Form with Validation380381```typescript382interface FormData {383 name: string384 description: string385 endDate: string386}387388interface FormErrors {389 name?: string390 description?: string391 endDate?: string392}393394export function CreateMarketForm() {395 const [formData, setFormData] = useState<FormData>({396 name: '',397 description: '',398 endDate: ''399 })400401 const [errors, setErrors] = useState<FormErrors>({})402403 const validate = (): boolean => {404 const newErrors: FormErrors = {}405406 if (!formData.name.trim()) {407 newErrors.name = 'Name is required'408 } else if (formData.name.length > 200) {409 newErrors.name = 'Name must be under 200 characters'410 }411412 if (!formData.description.trim()) {413 newErrors.description = 'Description is required'414 }415416 if (!formData.endDate) {417 newErrors.endDate = 'End date is required'418 }419420 setErrors(newErrors)421 return Object.keys(newErrors).length === 0422 }423424 const handleSubmit = async (e: React.FormEvent) => {425 e.preventDefault()426427 if (!validate()) return428429 try {430 await createMarket(formData)431 // Success handling432 } catch (error) {433 // Error handling434 }435 }436437 return (438 <form onSubmit={handleSubmit}>439 <input440 value={formData.name}441 onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}442 placeholder="Market name"443 />444 {errors.name && <span className="error">{errors.name}</span>}445446 {/* Other fields */}447448 <button type="submit">Create Market</button>449 </form>450 )451}452```453454## Error Boundary Pattern455456```typescript457interface ErrorBoundaryState {458 hasError: boolean459 error: Error | null460}461462export class ErrorBoundary extends React.Component<463 { children: React.ReactNode },464 ErrorBoundaryState465> {466 state: ErrorBoundaryState = {467 hasError: false,468 error: null469 }470471 static getDerivedStateFromError(error: Error): ErrorBoundaryState {472 return { hasError: true, error }473 }474475 componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {476 console.error('Error boundary caught:', error, errorInfo)477 }478479 render() {480 if (this.state.hasError) {481 return (482 <div className="error-fallback">483 <h2>Something went wrong</h2>484 <p>{this.state.error?.message}</p>485 <button onClick={() => this.setState({ hasError: false })}>486 Try again487 </button>488 </div>489 )490 }491492 return this.props.children493 }494}495496// Usage497<ErrorBoundary>498 <App />499</ErrorBoundary>500```501502## Animation Patterns503504### Framer Motion Animations505506```typescript507import { motion, AnimatePresence } from 'framer-motion'508509// ✅ List animations510export function AnimatedMarketList({ markets }: { markets: Market[] }) {511 return (512 <AnimatePresence>513 {markets.map(market => (514 <motion.div515 key={market.id}516 initial={{ opacity: 0, y: 20 }}517 animate={{ opacity: 1, y: 0 }}518 exit={{ opacity: 0, y: -20 }}519 transition={{ duration: 0.3 }}520 >521 <MarketCard market={market} />522 </motion.div>523 ))}524 </AnimatePresence>525 )526}527528// ✅ Modal animations529export function Modal({ isOpen, onClose, children }: ModalProps) {530 return (531 <AnimatePresence>532 {isOpen && (533 <>534 <motion.div535 className="modal-overlay"536 initial={{ opacity: 0 }}537 animate={{ opacity: 1 }}538 exit={{ opacity: 0 }}539 onClick={onClose}540 />541 <motion.div542 className="modal-content"543 initial={{ opacity: 0, scale: 0.9, y: 20 }}544 animate={{ opacity: 1, scale: 1, y: 0 }}545 exit={{ opacity: 0, scale: 0.9, y: 20 }}546 >547 {children}548 </motion.div>549 </>550 )}551 </AnimatePresence>552 )553}554```555556## Accessibility Patterns557558### Keyboard Navigation559560```typescript561export function Dropdown({ options, onSelect }: DropdownProps) {562 const [isOpen, setIsOpen] = useState(false)563 const [activeIndex, setActiveIndex] = useState(0)564565 const handleKeyDown = (e: React.KeyboardEvent) => {566 switch (e.key) {567 case 'ArrowDown':568 e.preventDefault()569 setActiveIndex(i => Math.min(i + 1, options.length - 1))570 break571 case 'ArrowUp':572 e.preventDefault()573 setActiveIndex(i => Math.max(i - 1, 0))574 break575 case 'Enter':576 e.preventDefault()577 onSelect(options[activeIndex])578 setIsOpen(false)579 break580 case 'Escape':581 setIsOpen(false)582 break583 }584 }585586 return (587 <div588 role="combobox"589 aria-expanded={isOpen}590 aria-haspopup="listbox"591 onKeyDown={handleKeyDown}592 >593 {/* Dropdown implementation */}594 </div>595 )596}597```598599### Focus Management600601```typescript602export function Modal({ isOpen, onClose, children }: ModalProps) {603 const modalRef = useRef<HTMLDivElement>(null)604 const previousFocusRef = useRef<HTMLElement | null>(null)605606 useEffect(() => {607 if (isOpen) {608 // Save currently focused element609 previousFocusRef.current = document.activeElement as HTMLElement610611 // Focus modal612 modalRef.current?.focus()613 } else {614 // Restore focus when closing615 previousFocusRef.current?.focus()616 }617 }, [isOpen])618619 return isOpen ? (620 <div621 ref={modalRef}622 role="dialog"623 aria-modal="true"624 tabIndex={-1}625 onKeyDown={e => e.key === 'Escape' && onClose()}626 >627 {children}628 </div>629 ) : null630}631```632633**Remember**: Modern frontend patterns enable maintainable, performant user interfaces. Choose patterns that fit your project complexity.
Full transparency — inspect the skill content before installing.