# React Practical Examples This file contains real-world examples of React patterns and solutions. ## Example 1: Custom Hook for Data Fetching ```typescript import { useState, useEffect } from 'react' interface FetchState { data: T | null loading: boolean error: Error | null } const useFetch = (url: string) => { const [state, setState] = useState>({ data: null, loading: true, error: null }) useEffect(() => { let cancelled = false const controller = new AbortController() const fetchData = async () => { try { setState(prev => ({ ...prev, loading: true, error: null })) const response = await fetch(url, { signal: controller.signal }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const data = await response.json() if (!cancelled) { setState({ data, loading: false, error: null }) } } catch (error) { if (!cancelled && error.name !== 'AbortError') { setState({ data: null, loading: false, error: error as Error }) } } } fetchData() return () => { cancelled = true controller.abort() } }, [url]) return state } // Usage const UserProfile = ({ userId }: { userId: string }) => { const { data, loading, error } = useFetch(`/api/users/${userId}`) if (loading) return if (error) return if (!data) return null return } ``` ## Example 2: Form with Validation ```typescript import { useState, useCallback } from 'react' import { z } from 'zod' const userSchema = z.object({ name: z.string().min(2, 'Name must be at least 2 characters'), email: z.string().email('Invalid email address'), age: z.number().min(18, 'Must be 18 or older') }) type UserForm = z.infer type FormErrors = Partial> const UserForm = () => { const [formData, setFormData] = useState({ name: '', email: '', age: 0 }) const [errors, setErrors] = useState({}) const [isSubmitting, setIsSubmitting] = useState(false) const handleChange = useCallback(( field: keyof UserForm, value: string | number ) => { setFormData(prev => ({ ...prev, [field]: value })) // Clear error when user starts typing setErrors(prev => ({ ...prev, [field]: undefined })) }, []) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() // Validate const result = userSchema.safeParse(formData) if (!result.success) { const fieldErrors: FormErrors = {} result.error.errors.forEach(err => { const field = err.path[0] as keyof UserForm fieldErrors[field] = err.message }) setErrors(fieldErrors) return } // Submit setIsSubmitting(true) try { await submitUser(result.data) // Success handling } catch (error) { console.error(error) } finally { setIsSubmitting(false) } } return (
handleChange('name', e.target.value)} /> {errors.name && {errors.name}}
handleChange('email', e.target.value)} /> {errors.email && {errors.email}}
handleChange('age', Number(e.target.value))} /> {errors.age && {errors.age}}
) } ``` ## Example 3: Modal with Portal ```typescript import { createPortal } from 'react-dom' import { useEffect, useRef, useState } from 'react' interface ModalProps { isOpen: boolean onClose: () => void children: React.ReactNode title?: string } const Modal = ({ isOpen, onClose, children, title }: ModalProps) => { const modalRef = useRef(null) // Close on Escape key useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } if (isOpen) { document.addEventListener('keydown', handleEscape) // Prevent body scroll document.body.style.overflow = 'hidden' } return () => { document.removeEventListener('keydown', handleEscape) document.body.style.overflow = 'unset' } }, [isOpen, onClose]) // Close on backdrop click const handleBackdropClick = (e: React.MouseEvent) => { if (e.target === modalRef.current) { onClose() } } if (!isOpen) return null return createPortal(
{title &&

{title}

}
{children}
, document.body ) } // Usage const App = () => { const [isOpen, setIsOpen] = useState(false) return ( <> setIsOpen(false)} title="My Modal">

Modal content goes here

) } ``` ## Example 4: Infinite Scroll ```typescript import { useState, useEffect, useRef, useCallback } from 'react' interface InfiniteScrollProps { fetchData: (page: number) => Promise renderItem: (item: T, index: number) => React.ReactNode loader?: React.ReactNode endMessage?: React.ReactNode } const InfiniteScroll = ({ fetchData, renderItem, loader =
Loading...
, endMessage =
No more items
}: InfiniteScrollProps) => { const [items, setItems] = useState([]) const [page, setPage] = useState(1) const [loading, setLoading] = useState(false) const [hasMore, setHasMore] = useState(true) const observerRef = useRef(null) const loadMoreRef = useRef(null) const loadMore = useCallback(async () => { if (loading || !hasMore) return setLoading(true) try { const newItems = await fetchData(page) if (newItems.length === 0) { setHasMore(false) } else { setItems(prev => [...prev, ...newItems]) setPage(prev => prev + 1) } } catch (error) { console.error('Failed to load items:', error) } finally { setLoading(false) } }, [page, loading, hasMore, fetchData]) // Set up intersection observer useEffect(() => { observerRef.current = new IntersectionObserver( entries => { if (entries[0].isIntersecting) { loadMore() } }, { threshold: 0.1 } ) const currentRef = loadMoreRef.current if (currentRef) { observerRef.current.observe(currentRef) } return () => { if (observerRef.current && currentRef) { observerRef.current.unobserve(currentRef) } } }, [loadMore]) // Initial load useEffect(() => { loadMore() }, []) return (
{items.map((item, index) => (
{renderItem(item, index)}
))}
{loading && loader} {!loading && !hasMore && endMessage}
) } // Usage const PostsList = () => { const fetchPosts = async (page: number) => { const response = await fetch(`/api/posts?page=${page}`) return response.json() } return ( fetchData={fetchPosts} renderItem={(post) => } /> ) } ``` ## Example 5: Dark Mode Toggle ```typescript import { createContext, useContext, useState, useEffect } from 'react' type Theme = 'light' | 'dark' interface ThemeContextType { theme: Theme toggleTheme: () => void } const ThemeContext = createContext(null) export const useTheme = () => { const context = useContext(ThemeContext) if (!context) { throw new Error('useTheme must be used within ThemeProvider') } return context } export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { const [theme, setTheme] = useState(() => { // Check localStorage and system preference const saved = localStorage.getItem('theme') as Theme | null if (saved) return saved if (window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark' } return 'light' }) useEffect(() => { // Update DOM and localStorage const root = document.documentElement root.classList.remove('light', 'dark') root.classList.add(theme) localStorage.setItem('theme', theme) }, [theme]) const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light') } return ( {children} ) } // Usage const ThemeToggle = () => { const { theme, toggleTheme } = useTheme() return ( ) } ``` ## Example 6: Debounced Search ```typescript import { useState, useEffect, useMemo } from 'react' const useDebounce = (value: T, delay: number): T => { const [debouncedValue, setDebouncedValue] = useState(value) useEffect(() => { const timer = setTimeout(() => { setDebouncedValue(value) }, delay) return () => { clearTimeout(timer) } }, [value, delay]) return debouncedValue } const SearchPage = () => { const [query, setQuery] = useState('') const [results, setResults] = useState([]) const [loading, setLoading] = useState(false) const debouncedQuery = useDebounce(query, 500) useEffect(() => { if (!debouncedQuery) { setResults([]) return } const searchProducts = async () => { setLoading(true) try { const response = await fetch(`/api/search?q=${debouncedQuery}`) const data = await response.json() setResults(data) } catch (error) { console.error('Search failed:', error) } finally { setLoading(false) } } searchProducts() }, [debouncedQuery]) return (
setQuery(e.target.value)} placeholder="Search products..." /> {loading && } {!loading && results.length > 0 && (
{results.map(product => ( ))}
)} {!loading && query && results.length === 0 && (

No results found for "{query}"

)}
) } ``` ## Example 7: Tabs Component ```typescript import { createContext, useContext, useState, useId } from 'react' interface TabsContextType { activeTab: string setActiveTab: (id: string) => void tabsId: string } const TabsContext = createContext(null) const useTabs = () => { const context = useContext(TabsContext) if (!context) throw new Error('Tabs compound components must be used within Tabs') return context } interface TabsProps { children: React.ReactNode defaultValue: string className?: string } const Tabs = ({ children, defaultValue, className }: TabsProps) => { const [activeTab, setActiveTab] = useState(defaultValue) const tabsId = useId() return (
{children}
) } const TabsList = ({ children, className }: { children: React.ReactNode className?: string }) => (
{children}
) interface TabsTriggerProps { value: string children: React.ReactNode className?: string } const TabsTrigger = ({ value, children, className }: TabsTriggerProps) => { const { activeTab, setActiveTab, tabsId } = useTabs() const isActive = activeTab === value return ( ) } interface TabsContentProps { value: string children: React.ReactNode className?: string } const TabsContent = ({ value, children, className }: TabsContentProps) => { const { activeTab, tabsId } = useTabs() if (activeTab !== value) return null return (
{children}
) } // Export compound component export { Tabs, TabsList, TabsTrigger, TabsContent } // Usage const App = () => ( Profile Settings Notifications

Profile Content

Settings Content

Notifications Content

) ``` ## Example 8: Error Boundary ```typescript import { Component, ErrorInfo, ReactNode } from 'react' interface Props { children: ReactNode fallback?: (error: Error, reset: () => void) => ReactNode onError?: (error: Error, errorInfo: ErrorInfo) => void } interface State { hasError: boolean error: Error | null } class ErrorBoundary extends Component { constructor(props: Props) { super(props) this.state = { hasError: false, error: null } } static getDerivedStateFromError(error: Error): State { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('ErrorBoundary caught:', error, errorInfo) this.props.onError?.(error, errorInfo) } reset = () => { this.setState({ hasError: false, error: null }) } render() { if (this.state.hasError && this.state.error) { if (this.props.fallback) { return this.props.fallback(this.state.error, this.reset) } return (

Something went wrong

Error details
{this.state.error.message}
) } return this.props.children } } // Usage const App = () => ( (

Oops! Something went wrong

{error.message}

)} onError={(error, errorInfo) => { // Send to error tracking service console.error('Error logged:', error, errorInfo) }} >
) ``` ## Example 9: Custom Hook for Local Storage ```typescript import { useState, useEffect, useCallback } from 'react' const useLocalStorage = ( key: string, initialValue: T ): [T, (value: T | ((val: T) => T)) => void, () => void] => { // Get initial value from localStorage const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key) return item ? JSON.parse(item) : initialValue } catch (error) { console.error(`Error loading ${key} from localStorage:`, error) return initialValue } }) // Update localStorage when value changes const setValue = useCallback((value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value setStoredValue(valueToStore) window.localStorage.setItem(key, JSON.stringify(valueToStore)) // Dispatch storage event for other tabs window.dispatchEvent(new Event('storage')) } catch (error) { console.error(`Error saving ${key} to localStorage:`, error) } }, [key, storedValue]) // Remove from localStorage const removeValue = useCallback(() => { try { window.localStorage.removeItem(key) setStoredValue(initialValue) } catch (error) { console.error(`Error removing ${key} from localStorage:`, error) } }, [key, initialValue]) // Listen for changes in other tabs useEffect(() => { const handleStorageChange = (e: StorageEvent) => { if (e.key === key && e.newValue) { setStoredValue(JSON.parse(e.newValue)) } } window.addEventListener('storage', handleStorageChange) return () => window.removeEventListener('storage', handleStorageChange) }, [key]) return [storedValue, setValue, removeValue] } // Usage const UserPreferences = () => { const [preferences, setPreferences, clearPreferences] = useLocalStorage('user-prefs', { theme: 'light', language: 'en', notifications: true }) return (
) } ``` ## Example 10: Optimistic Updates with useOptimistic ```typescript 'use client' import { useOptimistic } from 'react' import { likePost, unlikePost } from './actions' interface Post { id: string content: string likes: number isLiked: boolean } const PostCard = ({ post }: { post: Post }) => { const [optimisticPost, addOptimistic] = useOptimistic( post, (currentPost, update: Partial) => ({ ...currentPost, ...update }) ) const handleLike = async () => { // Optimistically update UI addOptimistic({ likes: optimisticPost.likes + 1, isLiked: true }) try { // Send server request await likePost(post.id) } catch (error) { // Server will send correct state via revalidation console.error('Failed to like post:', error) } } const handleUnlike = async () => { addOptimistic({ likes: optimisticPost.likes - 1, isLiked: false }) try { await unlikePost(post.id) } catch (error) { console.error('Failed to unlike post:', error) } } return (

{optimisticPost.content}

) } ``` ## References These examples demonstrate: - Custom hooks for reusable logic - Form handling with validation - Portal usage for modals - Infinite scroll with Intersection Observer - Context for global state - Debouncing for performance - Compound components pattern - Error boundaries - LocalStorage integration - Optimistic updates (React 19)