practical-patterns.tsx raw

   1  # React Practical Examples
   2  
   3  This file contains real-world examples of React patterns and solutions.
   4  
   5  ## Example 1: Custom Hook for Data Fetching
   6  
   7  ```typescript
   8  import { useState, useEffect } from 'react'
   9  
  10  interface FetchState<T> {
  11    data: T | null
  12    loading: boolean
  13    error: Error | null
  14  }
  15  
  16  const useFetch = <T,>(url: string) => {
  17    const [state, setState] = useState<FetchState<T>>({
  18      data: null,
  19      loading: true,
  20      error: null
  21    })
  22    
  23    useEffect(() => {
  24      let cancelled = false
  25      const controller = new AbortController()
  26      
  27      const fetchData = async () => {
  28        try {
  29          setState(prev => ({ ...prev, loading: true, error: null }))
  30          
  31          const response = await fetch(url, { 
  32            signal: controller.signal 
  33          })
  34          
  35          if (!response.ok) {
  36            throw new Error(`HTTP error! status: ${response.status}`)
  37          }
  38          
  39          const data = await response.json()
  40          
  41          if (!cancelled) {
  42            setState({ data, loading: false, error: null })
  43          }
  44        } catch (error) {
  45          if (!cancelled && error.name !== 'AbortError') {
  46            setState({ 
  47              data: null, 
  48              loading: false, 
  49              error: error as Error 
  50            })
  51          }
  52        }
  53      }
  54      
  55      fetchData()
  56      
  57      return () => {
  58        cancelled = true
  59        controller.abort()
  60      }
  61    }, [url])
  62    
  63    return state
  64  }
  65  
  66  // Usage
  67  const UserProfile = ({ userId }: { userId: string }) => {
  68    const { data, loading, error } = useFetch<User>(`/api/users/${userId}`)
  69    
  70    if (loading) return <Spinner />
  71    if (error) return <ErrorMessage error={error} />
  72    if (!data) return null
  73    
  74    return <UserCard user={data} />
  75  }
  76  ```
  77  
  78  ## Example 2: Form with Validation
  79  
  80  ```typescript
  81  import { useState, useCallback } from 'react'
  82  import { z } from 'zod'
  83  
  84  const userSchema = z.object({
  85    name: z.string().min(2, 'Name must be at least 2 characters'),
  86    email: z.string().email('Invalid email address'),
  87    age: z.number().min(18, 'Must be 18 or older')
  88  })
  89  
  90  type UserForm = z.infer<typeof userSchema>
  91  type FormErrors = Partial<Record<keyof UserForm, string>>
  92  
  93  const UserForm = () => {
  94    const [formData, setFormData] = useState<UserForm>({
  95      name: '',
  96      email: '',
  97      age: 0
  98    })
  99    const [errors, setErrors] = useState<FormErrors>({})
 100    const [isSubmitting, setIsSubmitting] = useState(false)
 101    
 102    const handleChange = useCallback((
 103      field: keyof UserForm,
 104      value: string | number
 105    ) => {
 106      setFormData(prev => ({ ...prev, [field]: value }))
 107      // Clear error when user starts typing
 108      setErrors(prev => ({ ...prev, [field]: undefined }))
 109    }, [])
 110    
 111    const handleSubmit = async (e: React.FormEvent) => {
 112      e.preventDefault()
 113      
 114      // Validate
 115      const result = userSchema.safeParse(formData)
 116      if (!result.success) {
 117        const fieldErrors: FormErrors = {}
 118        result.error.errors.forEach(err => {
 119          const field = err.path[0] as keyof UserForm
 120          fieldErrors[field] = err.message
 121        })
 122        setErrors(fieldErrors)
 123        return
 124      }
 125      
 126      // Submit
 127      setIsSubmitting(true)
 128      try {
 129        await submitUser(result.data)
 130        // Success handling
 131      } catch (error) {
 132        console.error(error)
 133      } finally {
 134        setIsSubmitting(false)
 135      }
 136    }
 137    
 138    return (
 139      <form onSubmit={handleSubmit}>
 140        <div>
 141          <label htmlFor="name">Name</label>
 142          <input
 143            id="name"
 144            value={formData.name}
 145            onChange={e => handleChange('name', e.target.value)}
 146          />
 147          {errors.name && <span className="error">{errors.name}</span>}
 148        </div>
 149        
 150        <div>
 151          <label htmlFor="email">Email</label>
 152          <input
 153            id="email"
 154            type="email"
 155            value={formData.email}
 156            onChange={e => handleChange('email', e.target.value)}
 157          />
 158          {errors.email && <span className="error">{errors.email}</span>}
 159        </div>
 160        
 161        <div>
 162          <label htmlFor="age">Age</label>
 163          <input
 164            id="age"
 165            type="number"
 166            value={formData.age || ''}
 167            onChange={e => handleChange('age', Number(e.target.value))}
 168          />
 169          {errors.age && <span className="error">{errors.age}</span>}
 170        </div>
 171        
 172        <button type="submit" disabled={isSubmitting}>
 173          {isSubmitting ? 'Submitting...' : 'Submit'}
 174        </button>
 175      </form>
 176    )
 177  }
 178  ```
 179  
 180  ## Example 3: Modal with Portal
 181  
 182  ```typescript
 183  import { createPortal } from 'react-dom'
 184  import { useEffect, useRef, useState } from 'react'
 185  
 186  interface ModalProps {
 187    isOpen: boolean
 188    onClose: () => void
 189    children: React.ReactNode
 190    title?: string
 191  }
 192  
 193  const Modal = ({ isOpen, onClose, children, title }: ModalProps) => {
 194    const modalRef = useRef<HTMLDivElement>(null)
 195    
 196    // Close on Escape key
 197    useEffect(() => {
 198      const handleEscape = (e: KeyboardEvent) => {
 199        if (e.key === 'Escape') onClose()
 200      }
 201      
 202      if (isOpen) {
 203        document.addEventListener('keydown', handleEscape)
 204        // Prevent body scroll
 205        document.body.style.overflow = 'hidden'
 206      }
 207      
 208      return () => {
 209        document.removeEventListener('keydown', handleEscape)
 210        document.body.style.overflow = 'unset'
 211      }
 212    }, [isOpen, onClose])
 213    
 214    // Close on backdrop click
 215    const handleBackdropClick = (e: React.MouseEvent) => {
 216      if (e.target === modalRef.current) {
 217        onClose()
 218      }
 219    }
 220    
 221    if (!isOpen) return null
 222    
 223    return createPortal(
 224      <div
 225        ref={modalRef}
 226        className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
 227        onClick={handleBackdropClick}
 228      >
 229        <div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
 230          <div className="flex justify-between items-center mb-4">
 231            {title && <h2 className="text-xl font-bold">{title}</h2>}
 232            <button
 233              onClick={onClose}
 234              className="text-gray-500 hover:text-gray-700"
 235              aria-label="Close modal"
 236            >
 237   238            </button>
 239          </div>
 240          {children}
 241        </div>
 242      </div>,
 243      document.body
 244    )
 245  }
 246  
 247  // Usage
 248  const App = () => {
 249    const [isOpen, setIsOpen] = useState(false)
 250    
 251    return (
 252      <>
 253        <button onClick={() => setIsOpen(true)}>Open Modal</button>
 254        <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="My Modal">
 255          <p>Modal content goes here</p>
 256          <button onClick={() => setIsOpen(false)}>Close</button>
 257        </Modal>
 258      </>
 259    )
 260  }
 261  ```
 262  
 263  ## Example 4: Infinite Scroll
 264  
 265  ```typescript
 266  import { useState, useEffect, useRef, useCallback } from 'react'
 267  
 268  interface InfiniteScrollProps<T> {
 269    fetchData: (page: number) => Promise<T[]>
 270    renderItem: (item: T, index: number) => React.ReactNode
 271    loader?: React.ReactNode
 272    endMessage?: React.ReactNode
 273  }
 274  
 275  const InfiniteScroll = <T extends { id: string | number },>({
 276    fetchData,
 277    renderItem,
 278    loader = <div>Loading...</div>,
 279    endMessage = <div>No more items</div>
 280  }: InfiniteScrollProps<T>) => {
 281    const [items, setItems] = useState<T[]>([])
 282    const [page, setPage] = useState(1)
 283    const [loading, setLoading] = useState(false)
 284    const [hasMore, setHasMore] = useState(true)
 285    const observerRef = useRef<IntersectionObserver | null>(null)
 286    const loadMoreRef = useRef<HTMLDivElement>(null)
 287    
 288    const loadMore = useCallback(async () => {
 289      if (loading || !hasMore) return
 290      
 291      setLoading(true)
 292      try {
 293        const newItems = await fetchData(page)
 294        
 295        if (newItems.length === 0) {
 296          setHasMore(false)
 297        } else {
 298          setItems(prev => [...prev, ...newItems])
 299          setPage(prev => prev + 1)
 300        }
 301      } catch (error) {
 302        console.error('Failed to load items:', error)
 303      } finally {
 304        setLoading(false)
 305      }
 306    }, [page, loading, hasMore, fetchData])
 307    
 308    // Set up intersection observer
 309    useEffect(() => {
 310      observerRef.current = new IntersectionObserver(
 311        entries => {
 312          if (entries[0].isIntersecting) {
 313            loadMore()
 314          }
 315        },
 316        { threshold: 0.1 }
 317      )
 318      
 319      const currentRef = loadMoreRef.current
 320      if (currentRef) {
 321        observerRef.current.observe(currentRef)
 322      }
 323      
 324      return () => {
 325        if (observerRef.current && currentRef) {
 326          observerRef.current.unobserve(currentRef)
 327        }
 328      }
 329    }, [loadMore])
 330    
 331    // Initial load
 332    useEffect(() => {
 333      loadMore()
 334    }, [])
 335    
 336    return (
 337      <div>
 338        {items.map((item, index) => (
 339          <div key={item.id}>
 340            {renderItem(item, index)}
 341          </div>
 342        ))}
 343        
 344        <div ref={loadMoreRef}>
 345          {loading && loader}
 346          {!loading && !hasMore && endMessage}
 347        </div>
 348      </div>
 349    )
 350  }
 351  
 352  // Usage
 353  const PostsList = () => {
 354    const fetchPosts = async (page: number) => {
 355      const response = await fetch(`/api/posts?page=${page}`)
 356      return response.json()
 357    }
 358    
 359    return (
 360      <InfiniteScroll<Post>
 361        fetchData={fetchPosts}
 362        renderItem={(post) => <PostCard post={post} />}
 363      />
 364    )
 365  }
 366  ```
 367  
 368  ## Example 5: Dark Mode Toggle
 369  
 370  ```typescript
 371  import { createContext, useContext, useState, useEffect } from 'react'
 372  
 373  type Theme = 'light' | 'dark'
 374  
 375  interface ThemeContextType {
 376    theme: Theme
 377    toggleTheme: () => void
 378  }
 379  
 380  const ThemeContext = createContext<ThemeContextType | null>(null)
 381  
 382  export const useTheme = () => {
 383    const context = useContext(ThemeContext)
 384    if (!context) {
 385      throw new Error('useTheme must be used within ThemeProvider')
 386    }
 387    return context
 388  }
 389  
 390  export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
 391    const [theme, setTheme] = useState<Theme>(() => {
 392      // Check localStorage and system preference
 393      const saved = localStorage.getItem('theme') as Theme | null
 394      if (saved) return saved
 395      
 396      if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
 397        return 'dark'
 398      }
 399      
 400      return 'light'
 401    })
 402    
 403    useEffect(() => {
 404      // Update DOM and localStorage
 405      const root = document.documentElement
 406      root.classList.remove('light', 'dark')
 407      root.classList.add(theme)
 408      localStorage.setItem('theme', theme)
 409    }, [theme])
 410    
 411    const toggleTheme = () => {
 412      setTheme(prev => prev === 'light' ? 'dark' : 'light')
 413    }
 414    
 415    return (
 416      <ThemeContext.Provider value={{ theme, toggleTheme }}>
 417        {children}
 418      </ThemeContext.Provider>
 419    )
 420  }
 421  
 422  // Usage
 423  const ThemeToggle = () => {
 424    const { theme, toggleTheme } = useTheme()
 425    
 426    return (
 427      <button onClick={toggleTheme} aria-label="Toggle theme">
 428        {theme === 'light' ? '🌙' : '☀️'}
 429      </button>
 430    )
 431  }
 432  ```
 433  
 434  ## Example 6: Debounced Search
 435  
 436  ```typescript
 437  import { useState, useEffect, useMemo } from 'react'
 438  
 439  const useDebounce = <T,>(value: T, delay: number): T => {
 440    const [debouncedValue, setDebouncedValue] = useState(value)
 441    
 442    useEffect(() => {
 443      const timer = setTimeout(() => {
 444        setDebouncedValue(value)
 445      }, delay)
 446      
 447      return () => {
 448        clearTimeout(timer)
 449      }
 450    }, [value, delay])
 451    
 452    return debouncedValue
 453  }
 454  
 455  const SearchPage = () => {
 456    const [query, setQuery] = useState('')
 457    const [results, setResults] = useState<Product[]>([])
 458    const [loading, setLoading] = useState(false)
 459    
 460    const debouncedQuery = useDebounce(query, 500)
 461    
 462    useEffect(() => {
 463      if (!debouncedQuery) {
 464        setResults([])
 465        return
 466      }
 467      
 468      const searchProducts = async () => {
 469        setLoading(true)
 470        try {
 471          const response = await fetch(`/api/search?q=${debouncedQuery}`)
 472          const data = await response.json()
 473          setResults(data)
 474        } catch (error) {
 475          console.error('Search failed:', error)
 476        } finally {
 477          setLoading(false)
 478        }
 479      }
 480      
 481      searchProducts()
 482    }, [debouncedQuery])
 483    
 484    return (
 485      <div>
 486        <input
 487          type="search"
 488          value={query}
 489          onChange={e => setQuery(e.target.value)}
 490          placeholder="Search products..."
 491        />
 492        
 493        {loading && <Spinner />}
 494        
 495        {!loading && results.length > 0 && (
 496          <div>
 497            {results.map(product => (
 498              <ProductCard key={product.id} product={product} />
 499            ))}
 500          </div>
 501        )}
 502        
 503        {!loading && query && results.length === 0 && (
 504          <p>No results found for "{query}"</p>
 505        )}
 506      </div>
 507    )
 508  }
 509  ```
 510  
 511  ## Example 7: Tabs Component
 512  
 513  ```typescript
 514  import { createContext, useContext, useState, useId } from 'react'
 515  
 516  interface TabsContextType {
 517    activeTab: string
 518    setActiveTab: (id: string) => void
 519    tabsId: string
 520  }
 521  
 522  const TabsContext = createContext<TabsContextType | null>(null)
 523  
 524  const useTabs = () => {
 525    const context = useContext(TabsContext)
 526    if (!context) throw new Error('Tabs compound components must be used within Tabs')
 527    return context
 528  }
 529  
 530  interface TabsProps {
 531    children: React.ReactNode
 532    defaultValue: string
 533    className?: string
 534  }
 535  
 536  const Tabs = ({ children, defaultValue, className }: TabsProps) => {
 537    const [activeTab, setActiveTab] = useState(defaultValue)
 538    const tabsId = useId()
 539    
 540    return (
 541      <TabsContext.Provider value={{ activeTab, setActiveTab, tabsId }}>
 542        <div className={className}>
 543          {children}
 544        </div>
 545      </TabsContext.Provider>
 546    )
 547  }
 548  
 549  const TabsList = ({ children, className }: { 
 550    children: React.ReactNode
 551    className?: string 
 552  }) => (
 553    <div role="tablist" className={className}>
 554      {children}
 555    </div>
 556  )
 557  
 558  interface TabsTriggerProps {
 559    value: string
 560    children: React.ReactNode
 561    className?: string
 562  }
 563  
 564  const TabsTrigger = ({ value, children, className }: TabsTriggerProps) => {
 565    const { activeTab, setActiveTab, tabsId } = useTabs()
 566    const isActive = activeTab === value
 567    
 568    return (
 569      <button
 570        role="tab"
 571        id={`${tabsId}-tab-${value}`}
 572        aria-controls={`${tabsId}-panel-${value}`}
 573        aria-selected={isActive}
 574        onClick={() => setActiveTab(value)}
 575        className={`${className} ${isActive ? 'active' : ''}`}
 576      >
 577        {children}
 578      </button>
 579    )
 580  }
 581  
 582  interface TabsContentProps {
 583    value: string
 584    children: React.ReactNode
 585    className?: string
 586  }
 587  
 588  const TabsContent = ({ value, children, className }: TabsContentProps) => {
 589    const { activeTab, tabsId } = useTabs()
 590    
 591    if (activeTab !== value) return null
 592    
 593    return (
 594      <div
 595        role="tabpanel"
 596        id={`${tabsId}-panel-${value}`}
 597        aria-labelledby={`${tabsId}-tab-${value}`}
 598        className={className}
 599      >
 600        {children}
 601      </div>
 602    )
 603  }
 604  
 605  // Export compound component
 606  export { Tabs, TabsList, TabsTrigger, TabsContent }
 607  
 608  // Usage
 609  const App = () => (
 610    <Tabs defaultValue="profile">
 611      <TabsList>
 612        <TabsTrigger value="profile">Profile</TabsTrigger>
 613        <TabsTrigger value="settings">Settings</TabsTrigger>
 614        <TabsTrigger value="notifications">Notifications</TabsTrigger>
 615      </TabsList>
 616      
 617      <TabsContent value="profile">
 618        <h2>Profile Content</h2>
 619      </TabsContent>
 620      
 621      <TabsContent value="settings">
 622        <h2>Settings Content</h2>
 623      </TabsContent>
 624      
 625      <TabsContent value="notifications">
 626        <h2>Notifications Content</h2>
 627      </TabsContent>
 628    </Tabs>
 629  )
 630  ```
 631  
 632  ## Example 8: Error Boundary
 633  
 634  ```typescript
 635  import { Component, ErrorInfo, ReactNode } from 'react'
 636  
 637  interface Props {
 638    children: ReactNode
 639    fallback?: (error: Error, reset: () => void) => ReactNode
 640    onError?: (error: Error, errorInfo: ErrorInfo) => void
 641  }
 642  
 643  interface State {
 644    hasError: boolean
 645    error: Error | null
 646  }
 647  
 648  class ErrorBoundary extends Component<Props, State> {
 649    constructor(props: Props) {
 650      super(props)
 651      this.state = { hasError: false, error: null }
 652    }
 653    
 654    static getDerivedStateFromError(error: Error): State {
 655      return { hasError: true, error }
 656    }
 657    
 658    componentDidCatch(error: Error, errorInfo: ErrorInfo) {
 659      console.error('ErrorBoundary caught:', error, errorInfo)
 660      this.props.onError?.(error, errorInfo)
 661    }
 662    
 663    reset = () => {
 664      this.setState({ hasError: false, error: null })
 665    }
 666    
 667    render() {
 668      if (this.state.hasError && this.state.error) {
 669        if (this.props.fallback) {
 670          return this.props.fallback(this.state.error, this.reset)
 671        }
 672        
 673        return (
 674          <div className="error-boundary">
 675            <h2>Something went wrong</h2>
 676            <details>
 677              <summary>Error details</summary>
 678              <pre>{this.state.error.message}</pre>
 679            </details>
 680            <button onClick={this.reset}>Try again</button>
 681          </div>
 682        )
 683      }
 684      
 685      return this.props.children
 686    }
 687  }
 688  
 689  // Usage
 690  const App = () => (
 691    <ErrorBoundary
 692      fallback={(error, reset) => (
 693        <div>
 694          <h1>Oops! Something went wrong</h1>
 695          <p>{error.message}</p>
 696          <button onClick={reset}>Retry</button>
 697        </div>
 698      )}
 699      onError={(error, errorInfo) => {
 700        // Send to error tracking service
 701        console.error('Error logged:', error, errorInfo)
 702      }}
 703    >
 704      <YourApp />
 705    </ErrorBoundary>
 706  )
 707  ```
 708  
 709  ## Example 9: Custom Hook for Local Storage
 710  
 711  ```typescript
 712  import { useState, useEffect, useCallback } from 'react'
 713  
 714  const useLocalStorage = <T,>(
 715    key: string,
 716    initialValue: T
 717  ): [T, (value: T | ((val: T) => T)) => void, () => void] => {
 718    // Get initial value from localStorage
 719    const [storedValue, setStoredValue] = useState<T>(() => {
 720      try {
 721        const item = window.localStorage.getItem(key)
 722        return item ? JSON.parse(item) : initialValue
 723      } catch (error) {
 724        console.error(`Error loading ${key} from localStorage:`, error)
 725        return initialValue
 726      }
 727    })
 728    
 729    // Update localStorage when value changes
 730    const setValue = useCallback((value: T | ((val: T) => T)) => {
 731      try {
 732        const valueToStore = value instanceof Function ? value(storedValue) : value
 733        setStoredValue(valueToStore)
 734        window.localStorage.setItem(key, JSON.stringify(valueToStore))
 735        
 736        // Dispatch storage event for other tabs
 737        window.dispatchEvent(new Event('storage'))
 738      } catch (error) {
 739        console.error(`Error saving ${key} to localStorage:`, error)
 740      }
 741    }, [key, storedValue])
 742    
 743    // Remove from localStorage
 744    const removeValue = useCallback(() => {
 745      try {
 746        window.localStorage.removeItem(key)
 747        setStoredValue(initialValue)
 748      } catch (error) {
 749        console.error(`Error removing ${key} from localStorage:`, error)
 750      }
 751    }, [key, initialValue])
 752    
 753    // Listen for changes in other tabs
 754    useEffect(() => {
 755      const handleStorageChange = (e: StorageEvent) => {
 756        if (e.key === key && e.newValue) {
 757          setStoredValue(JSON.parse(e.newValue))
 758        }
 759      }
 760      
 761      window.addEventListener('storage', handleStorageChange)
 762      return () => window.removeEventListener('storage', handleStorageChange)
 763    }, [key])
 764    
 765    return [storedValue, setValue, removeValue]
 766  }
 767  
 768  // Usage
 769  const UserPreferences = () => {
 770    const [preferences, setPreferences, clearPreferences] = useLocalStorage('user-prefs', {
 771      theme: 'light',
 772      language: 'en',
 773      notifications: true
 774    })
 775    
 776    return (
 777      <div>
 778        <label>
 779          <input
 780            type="checkbox"
 781            checked={preferences.notifications}
 782            onChange={e => setPreferences({
 783              ...preferences,
 784              notifications: e.target.checked
 785            })}
 786          />
 787          Enable notifications
 788        </label>
 789        
 790        <button onClick={clearPreferences}>
 791          Reset to defaults
 792        </button>
 793      </div>
 794    )
 795  }
 796  ```
 797  
 798  ## Example 10: Optimistic Updates with useOptimistic
 799  
 800  ```typescript
 801  'use client'
 802  
 803  import { useOptimistic } from 'react'
 804  import { likePost, unlikePost } from './actions'
 805  
 806  interface Post {
 807    id: string
 808    content: string
 809    likes: number
 810    isLiked: boolean
 811  }
 812  
 813  const PostCard = ({ post }: { post: Post }) => {
 814    const [optimisticPost, addOptimistic] = useOptimistic(
 815      post,
 816      (currentPost, update: Partial<Post>) => ({
 817        ...currentPost,
 818        ...update
 819      })
 820    )
 821    
 822    const handleLike = async () => {
 823      // Optimistically update UI
 824      addOptimistic({ 
 825        likes: optimisticPost.likes + 1,
 826        isLiked: true 
 827      })
 828      
 829      try {
 830        // Send server request
 831        await likePost(post.id)
 832      } catch (error) {
 833        // Server will send correct state via revalidation
 834        console.error('Failed to like post:', error)
 835      }
 836    }
 837    
 838    const handleUnlike = async () => {
 839      addOptimistic({ 
 840        likes: optimisticPost.likes - 1,
 841        isLiked: false 
 842      })
 843      
 844      try {
 845        await unlikePost(post.id)
 846      } catch (error) {
 847        console.error('Failed to unlike post:', error)
 848      }
 849    }
 850    
 851    return (
 852      <div className="post-card">
 853        <p>{optimisticPost.content}</p>
 854        <button 
 855          onClick={optimisticPost.isLiked ? handleUnlike : handleLike}
 856          className={optimisticPost.isLiked ? 'liked' : ''}
 857        >
 858          ❤️ {optimisticPost.likes}
 859        </button>
 860      </div>
 861    )
 862  }
 863  ```
 864  
 865  ## References
 866  
 867  These examples demonstrate:
 868  - Custom hooks for reusable logic
 869  - Form handling with validation
 870  - Portal usage for modals
 871  - Infinite scroll with Intersection Observer
 872  - Context for global state
 873  - Debouncing for performance
 874  - Compound components pattern
 875  - Error boundaries
 876  - LocalStorage integration
 877  - Optimistic updates (React 19)
 878  
 879