Skip to main content
Glama
frontend-development-modern-web-2025.md16.6 kB
# Frontend Development & Modern Web 2025 **Updated**: 2025-11-24 | **Focus**: React, TypeScript, Performance, Responsive Design --- ## React Fundamentals ```typescript // COMPONENTS (Function components, modern approach) import React, { useState, useEffect } from 'react'; // Props & TypeScript interface UserCardProps { name: string; email: string; avatar?: string; // Optional onEdit?: () => void; } function UserCard({ name, email, avatar, onEdit }: UserCardProps) { return ( <div className="user-card"> {avatar && <img src={avatar} alt={name} />} <h3>{name}</h3> <p>{email}</p> {onEdit && <button onClick={onEdit}>Edit</button>} </div> ); } --- // STATE (useState) function Counter() { const [count, setCount] = useState(0); // Initial state = 0 const increment = () => { setCount(count + 1); // Or: setCount(prev => prev + 1); // Functional update (safer) }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>+1</button> </div> ); } // Multiple state variables function Form() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [submitted, setSubmitted] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setSubmitted(true); }; if (submitted) { return <div>Thanks, {name}!</div>; } return ( <form onSubmit={handleSubmit}> <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" /> <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" type="email" /> <button type="submit">Submit</button> </form> ); } --- // EFFECTS (useEffect - side effects) function UserProfile({ userId }: { userId: number }) { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { // Runs after render async function fetchUser() { setLoading(true); const response = await fetch(`/api/users/${userId}`); const data = await response.json(); setUser(data); setLoading(false); } fetchUser(); // Cleanup (runs before next effect or unmount) return () => { // Cancel requests, clear timers, etc. }; }, [userId]); // Dependency array: re-run when userId changes if (loading) return <div>Loading...</div>; if (!user) return <div>User not found</div>; return <div>{user.name}</div>; } // useEffect patterns: useEffect(() => {}, []); // Runs once (component mount) useEffect(() => {}); // Runs every render (usually wrong!) useEffect(() => {}, [dep1, dep2]); // Runs when deps change --- // CUSTOM HOOKS (Reusable logic) function useFetch<T>(url: string) { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { async function fetchData() { try { setLoading(true); const response = await fetch(url); if (!response.ok) throw new Error('Network error'); const json = await response.json(); setData(json); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoading(false); } } fetchData(); }, [url]); return { data, loading, error }; } // Usage function UserList() { const { data: users, loading, error } = useFetch<User[]>('/api/users'); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return ( <ul> {users?.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); } --- // CONTEXT (Global state, avoid prop drilling) interface ThemeContextType { theme: 'light' | 'dark'; toggleTheme: () => void; } const ThemeContext = React.createContext<ThemeContextType | undefined>(undefined); function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<'light' | 'dark'>('light'); const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); } // Custom hook to use context function useTheme() { const context = React.useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; } // Usage function App() { return ( <ThemeProvider> <Header /> <Main /> </ThemeProvider> ); } function Header() { const { theme, toggleTheme } = useTheme(); return ( <header className={theme}> <button onClick={toggleTheme}> Switch to {theme === 'light' ? 'dark' : 'light'} mode </button> </header> ); } --- // REFS (Access DOM elements, persist values across renders) function TextInput() { const inputRef = useRef<HTMLInputElement>(null); const focusInput = () => { inputRef.current?.focus(); }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={focusInput}>Focus Input</button> </div> ); } // Persist value without triggering re-render function Timer() { const [count, setCount] = useState(0); const intervalRef = useRef<number | null>(null); const startTimer = () => { intervalRef.current = window.setInterval(() => { setCount(c => c + 1); }, 1000); }; const stopTimer = () => { if (intervalRef.current) { clearInterval(intervalRef.current); } }; useEffect(() => { return () => stopTimer(); // Cleanup }, []); return ( <div> <p>{count} seconds</p> <button onClick={startTimer}>Start</button> <button onClick={stopTimer}>Stop</button> </div> ); } --- // PERFORMANCE (useMemo, useCallback) // useMemo: Memoize expensive calculations function SearchResults({ query, items }: { query: string; items: Item[] }) { const filteredItems = useMemo(() => { // Only recalculate when query or items change return items.filter(item => item.name.toLowerCase().includes(query.toLowerCase()) ); }, [query, items]); return ( <ul> {filteredItems.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> ); } // useCallback: Memoize function (prevent re-creating on every render) function Parent() { const [count, setCount] = useState(0); // Without useCallback, new function created every render // Child re-renders even if props don't change (reference changed) const handleClick = useCallback(() => { console.log('Button clicked'); }, []); // No dependencies = function never changes return ( <div> <p>{count}</p> <button onClick={() => setCount(c => c + 1)}>Increment</button> <Child onClick={handleClick} /> </div> ); } const Child = React.memo(({ onClick }: { onClick: () => void }) => { console.log('Child rendered'); return <button onClick={onClick}>Click me</button>; }); ``` --- ## TypeScript for React ```typescript // TYPES & INTERFACES // Component props interface ButtonProps { label: string; onClick: () => void; disabled?: boolean; variant?: 'primary' | 'secondary'; } // Function component type const Button: React.FC<ButtonProps> = ({ label, onClick, disabled, variant = 'primary' }) => { return ( <button onClick={onClick} disabled={disabled} className={`btn btn-${variant}`} > {label} </button> ); }; --- // EVENT TYPES function Form() { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); // ... }; const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { console.log(e.target.value); }; const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { console.log('Clicked'); }; return ( <form onSubmit={handleSubmit}> <input onChange={handleChange} /> <button onClick={handleClick}>Submit</button> </form> ); } --- // GENERICS interface ListProps<T> { items: T[]; renderItem: (item: T) => React.ReactNode; } function List<T>({ items, renderItem }: ListProps<T>) { return ( <ul> {items.map((item, index) => ( <li key={index}>{renderItem(item)}</li> ))} </ul> ); } // Usage <List items={[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]} renderItem={(user) => <span>{user.name}</span>} /> --- // TYPE vs INTERFACE // Use interface for objects (extensible) interface User { id: number; name: string; } interface Admin extends User { role: 'admin'; } // Use type for unions, intersections, primitives type Status = 'idle' | 'loading' | 'success' | 'error'; type UserOrAdmin = User | Admin; type UserWithTimestamps = User & { createdAt: Date; updatedAt: Date; }; ``` --- ## CSS & Styling ```typescript // TAILWIND CSS (Utility-first) function Card() { return ( <div className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow"> <h2 className="text-2xl font-bold text-gray-900 mb-2">Title</h2> <p className="text-gray-600">Description</p> <button className="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"> Action </button> </div> ); } // Responsive <div className="text-sm md:text-base lg:text-lg"> Responsive text </div> // Dark mode <div className="bg-white dark:bg-gray-800 text-black dark:text-white"> Auto dark mode </div> --- // CSS MODULES (Scoped CSS) // styles.module.css .container { padding: 20px; background: white; } .title { font-size: 24px; font-weight: bold; } // Component.tsx import styles from './styles.module.css'; function Component() { return ( <div className={styles.container}> <h1 className={styles.title}>Hello</h1> </div> ); } --- // STYLED-COMPONENTS (CSS-in-JS) import styled from 'styled-components'; const Button = styled.button<{ primary?: boolean }>` background: ${props => props.primary ? '#0070f3' : '#eaeaea'}; color: ${props => props.primary ? 'white' : 'black'}; padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer; &:hover { opacity: 0.8; } `; // Usage <Button primary>Primary</Button> <Button>Secondary</Button> ``` --- ## Performance Optimization ```typescript // CODE SPLITTING (Lazy load components) import React, { Suspense, lazy } from 'react'; const HeavyComponent = lazy(() => import('./HeavyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <HeavyComponent /> </Suspense> ); } // Route-based code splitting (React Router) const Home = lazy(() => import('./pages/Home')); const About = lazy(() => import('./pages/About')); <Routes> <Route path="/" element={<Suspense fallback={<Loading />}><Home /></Suspense>} /> <Route path="/about" element={<Suspense fallback={<Loading />}><About /></Suspense>} /> </Routes> --- // IMAGE OPTIMIZATION // Next.js Image component (automatic optimization) import Image from 'next/image'; <Image src="/profile.jpg" alt="Profile" width={500} height={500} priority // Load immediately (above fold) placeholder="blur" // Show blur while loading /> // Lazy load images (vanilla) <img src="placeholder.jpg" data-src="actual-image.jpg" loading="lazy" alt="Description" /> --- // BUNDLE SIZE // Tree shaking (import only what you need) // ❌ Imports entire library import _ from 'lodash'; _.debounce(fn, 300); // ✅ Import specific function import debounce from 'lodash/debounce'; debounce(fn, 300); // Analyze bundle npm run build npx vite-bundle-visualizer # Vite npx webpack-bundle-analyzer # Webpack --- // WEB VITALS (Performance metrics) // Largest Contentful Paint (LCP): <2.5s // First Input Delay (FID): <100ms // Cumulative Layout Shift (CLS): <0.1 // Measure import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'; getCLS(console.log); getFID(console.log); getLCP(console.log); ``` --- ## Responsive Design ```css /* MOBILE-FIRST (Start with mobile, add desktop) */ /* Mobile (default) */ .container { padding: 16px; font-size: 14px; } /* Tablet (768px+) */ @media (min-width: 768px) { .container { padding: 24px; font-size: 16px; } } /* Desktop (1024px+) */ @media (min-width: 1024px) { .container { padding: 32px; font-size: 18px; max-width: 1200px; margin: 0 auto; } } --- /* FLEXBOX */ .container { display: flex; gap: 16px; flex-wrap: wrap; /* Wrap on small screens */ } .item { flex: 1 1 300px; /* Grow, shrink, base width */ /* = min-width: 300px, expands to fill */ } --- /* GRID */ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); /* Auto-fit: Columns adjust to container */ /* minmax: Min 250px, max 1fr (equal) */ gap: 20px; } --- /* VIEWPORT UNITS */ .hero { height: 100vh; /* 100% of viewport height */ width: 100vw; /* 100% of viewport width */ } .responsive-text { font-size: clamp(16px, 2vw, 24px); /* Min 16px, preferred 2% of viewport, max 24px */ } --- /* CONTAINER QUERIES (New, modern browsers) */ .card-container { container-type: inline-size; } @container (min-width: 400px) { .card { display: grid; grid-template-columns: 1fr 2fr; } } ``` --- ## Accessibility (a11y) ```typescript // SEMANTIC HTML // ❌ Bad (divs for everything) <div onClick={handleClick}>Button</div> // ✅ Good (semantic elements) <button onClick={handleClick}>Button</button> --- // ARIA ATTRIBUTES // Button with icon only <button aria-label="Close"> <XIcon /> </button> // Loading state <button aria-busy={loading} disabled={loading}> {loading ? 'Loading...' : 'Submit'} </button> // Expanded state (dropdown) <button aria-expanded={isOpen} aria-controls="menu-id"> Menu </button> <div id="menu-id" hidden={!isOpen}> {/* Menu items */} </div> --- // KEYBOARD NAVIGATION function Modal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { const modalRef = useRef<HTMLDivElement>(null); useEffect(() => { if (!isOpen) return; // Focus trap const focusableElements = modalRef.current?.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const firstElement = focusableElements?.[0] as HTMLElement; const lastElement = focusableElements?.[focusableElements.length - 1] as HTMLElement; firstElement?.focus(); const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose(); } if (e.key === 'Tab') { if (e.shiftKey && document.activeElement === firstElement) { e.preventDefault(); lastElement?.focus(); } else if (!e.shiftKey && document.activeElement === lastElement) { e.preventDefault(); firstElement?.focus(); } } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]); if (!isOpen) return null; return ( <div ref={modalRef} role="dialog" aria-modal="true" aria-labelledby="modal-title" > <h2 id="modal-title">Modal Title</h2> <button onClick={onClose}>Close</button> </div> ); } --- // FORM ACCESSIBILITY <form> <label htmlFor="email">Email</label> <input id="email" type="email" aria-required="true" aria-invalid={hasError} aria-describedby={hasError ? 'email-error' : undefined} /> {hasError && <span id="email-error" role="alert">Invalid email</span>} </form> ``` --- ## Key Takeaways 1. **Component thinking** - Reusable, composable, single responsibility 2. **TypeScript** - Catch errors early, better autocomplete, self-documenting 3. **Performance** - Code splitting, lazy loading, optimize images (80% users mobile) 4. **Responsive** - Mobile-first, test on real devices (not just browser resize) 5. **Accessibility** - Not optional (legal requirement, moral imperative, better UX for all) --- ## References - React Documentation (react.dev) - TypeScript Handbook - "Learning React" - Alex Banks & Eve Porcello **Related**: `react-patterns.md`, `nextjs-fullstack.md`, `performance-optimization.md`

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/seanshin0214/persona-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server