React Patterns
Reusable patterns for building flexible, maintainable React interfaces.
๐งฉ Compound Components
Build components that talk to each other like best friends! ๐ค Compound Components let you create flexible, composable APIs where parent and children share state seamlessly through Context. It's like LEGO blocks for your UI! ๐งฑ
๐ Impact: HIGH โ The secret sauce behind every great component library. Master this and you'll build APIs that developers actually enjoy using! ๐ฏ
๐ In this section: Context-based state sharing โข Composable sub-components โข Flexible API design โข Real-world Accordion example
What to notice
- Single component with all props; rigid structure.
// โ WRONG: Single component with all props - not flexibleconst faqItems = [{ question: "What is React?", answer: "A UI library for building interfaces." },{ question: "Why use it?", answer: "Component-based, reusable, and declarative." },];function Accordion({items = [],titleKey,contentKey,defaultOpen = 0}) {const [openIndex, setOpenIndex] = useState(defaultOpen);return (<div>{items.map((item, index) => (<div key={index}><button onClick={() => setOpenIndex(index)}>{item[titleKey]}</button>{openIndex === index && (<div>{item[contentKey]}</div>)}</div>))}</div>);}// Usage - rigid structure (faqItems must match titleKey/contentKey)function App() {return (<Accordionitems={faqItems}titleKey="question"contentKey="answer"defaultOpen={0}/>);}export default App;// Problems:// - Can't customize individual items easily// - Can't rearrange header/content order// - Hard to add custom styling per item// - Limited flexibility for different use cases
What to notice
- Compound components with context; flexible structure.
// โ GOOD: Compound components - flexible and composableimport { createContext, useContext, useState } from 'react';const AccordionContext = createContext<{openIndex: number;setOpenIndex: (index: number) => void;} | null>(null);function Accordion({ children, defaultOpen = 0 }) {const [openIndex, setOpenIndex] = useState(defaultOpen);return (<AccordionContext.Provider value={{ openIndex, setOpenIndex }}><div className="accordion">{children}</div></AccordionContext.Provider>);}function AccordionItem({ index, children }) {const context = useContext(AccordionContext);if (!context) throw new Error('AccordionItem must be inside Accordion');const { openIndex, setOpenIndex } = context;const isOpen = openIndex === index;return (<div className="accordion-item">{React.Children.map(children, child =>React.cloneElement(child, { isOpen, onToggle: () => setOpenIndex(index) }))}</div>);}function AccordionHeader({ children, isOpen, onToggle }) {return (<buttononClick={onToggle}className={`accordion-header ${isOpen ? 'open' : ''}`}>{children}</button>);}function AccordionContent({ children, isOpen }) {if (!isOpen) return null;return <div className="accordion-content">{children}</div>;}Accordion.Item = AccordionItem;Accordion.Header = AccordionHeader;Accordion.Content = AccordionContent;function Icon() { return <span>๐ </span>; }function CustomComponent() { return <p>Custom content here</p>; }function App() {return (<Accordion defaultOpen={0}><Accordion.Item index={0}><Accordion.Header><Icon /> Custom Question with Icon</Accordion.Header><Accordion.Content><p>Answer with any content</p><button>Learn More</button></Accordion.Content></Accordion.Item><Accordion.Item index={1}><Accordion.Header>Another Question</Accordion.Header><Accordion.Content><CustomComponent /></Accordion.Content></Accordion.Item></Accordion>);}export default App;// Benefits:// โ Full control over structure// โ Easy to customize each item// โ State shared automatically via Context
๐ช Hooks
Stop copy-pasting the same useState + useEffect combo everywhere! ๐ Custom Hooks let you extract reusable logic into clean, testable functions. Write it once, use it everywhere โ your future self will thank you! ๐
๐ด Impact: CRITICAL โ The #1 pattern every React developer must master. If you're duplicating logic across components, you're doing it wrong! ๐จ
๐ In this section: Extracting reusable logic โข useUser custom hook โข Eliminating code duplication โข Shared state patterns
Extract logic when you repeat it across components (data, auth, forms). One hook = single source of truth and simpler tests.
// โ WRONG: Logic duplicated in multiple components// Mock API for demo (in real app this would be fetch)const mockFetchUser = (id) => Promise.resolve({ name: 'Jane Doe', avatar: 'https://via.placeholder.com/48' });function UserProfile({ userId }) {const [user, setUser] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {setLoading(true);mockFetchUser(userId).then(data => { setUser(data); setLoading(false); }).catch(err => { setError(err.message); setLoading(false); });}, [userId]);if (loading) return <div>Loading...</div>;if (error) return <div>Error: {error}</div>;return <div>{user.name}</div>;}function UserAvatar({ userId }) {const [user, setUser] = useState(null);const [loading, setLoading] = useState(true);useEffect(() => {mockFetchUser(userId).then(setUser).finally(() => setLoading(false));}, [userId]);if (loading) return <div style={{ width: 48, height: 48, background: '#333', borderRadius: 24 }} />;return <span>{user?.name} (avatar)</span>;}function App() {return (<div><UserProfile userId="1" /><UserAvatar userId="1" /></div>);}export default App;// Problems: Code duplication, hard to maintain, logic not reusable
// โ GOOD: Extract logic into custom hook// Mock API for democonst mockFetchUser = (id) => Promise.resolve({ name: 'Jane Doe', avatar: 'https://via.placeholder.com/48' });function Skeleton() {return <div style={{ width: 48, height: 48, background: '#333', borderRadius: 24 }} />;}function useUser(userId) {const [user, setUser] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {setLoading(true);setError(null);mockFetchUser(userId).then(data => { setUser(data); setLoading(false); }).catch(err => { setError(err.message); setLoading(false); });}, [userId]);return { user, loading, error };}function UserProfile({ userId }) {const { user, loading, error } = useUser(userId);if (loading) return <div>Loading...</div>;if (error) return <div>Error: {error}</div>;return <div>{user.name}</div>;}function UserAvatar({ userId }) {const { user, loading } = useUser(userId);if (loading) return <Skeleton />;return <span>{user?.name} (avatar)</span>;}function App() {return (<div><UserProfile userId="1" /><UserAvatar userId="1" /></div>);}export default App;
๐ HOC
Wrap your components in superpowers! ๐ฆธ HOCs are like gift wrapping โ they take a component and return an enhanced version with extra abilities (auth, logging, theming). Think of them as component decorators! โจ
๐ต Impact: MEDIUM โ A classic pattern that's still relevant for cross-cutting concerns like auth guards and analytics wrappers! ๐
๐ In this section: Authentication HOC โข Component enhancement โข Cross-cutting concerns โข withAuth pattern
Use them to add cross-cutting concerns (authentication, permissions, logging) without duplicating logic on every screen.
// โ WRONG: Authentication logic duplicatedconst checkAuth = () => Promise.resolve({ name: 'Demo User' });const redirect = () => {};function Dashboard() {const [user, setUser] = useState(null);const [loading, setLoading] = useState(true);useEffect(() => {checkAuth().then(u => {if (!u) { redirect('/login'); return; }setUser(u);setLoading(false);});}, []);if (loading) return <div>Loading...</div>;return <div>Dashboard Content</div>;}function Profile() {const [user, setUser] = useState(null);const [loading, setLoading] = useState(true);useEffect(() => {checkAuth().then(u => {if (!u) { redirect('/login'); return; }setUser(u);setLoading(false);});}, []);if (loading) return <div>Loading...</div>;return <div>Profile</div>;}function App() {return (<div><Dashboard /><Profile /></div>);}export default App;
// โ GOOD: Use HOC to add authenticationconst checkAuth = () => Promise.resolve({ name: 'Demo User' });const useRouter = () => ({ push: () => {} });function withAuth(Component) {return function AuthenticatedComponent(props) {const [user, setUser] = useState(null);const [loading, setLoading] = useState(true);const router = useRouter();useEffect(() => {checkAuth().then(u => {if (!u) { router.push('/login'); return; }setUser(u);setLoading(false);}).catch(() => router.push('/login'));}, []);if (loading) return <div>Loading...</div>;if (!user) return null;return <Component {...props} user={user} />;};}function Dashboard({ user }) {return <div>Dashboard: {user?.name}</div>;}function Profile({ user }) {return <div>Profile: {user?.name}</div>;}function Settings({ user }) {return <div>Settings: {user?.name}</div>;}const ProtectedDashboard = withAuth(Dashboard);const ProtectedProfile = withAuth(Profile);const ProtectedSettings = withAuth(Settings);function App() {return (<div><ProtectedDashboard /><ProtectedProfile /><ProtectedSettings /></div>);}export default App;
๐จ Render Props
Pass a function as children and let the consumer decide what to render! ๐๏ธ Render Props give you maximum flexibility โ the component handles the logic, and YOU choose the presentation. It's like a choose-your-own-adventure for UI! ๐
๐ต Impact: MEDIUM โ The OG flexibility pattern! Still powerful for sharing behavior between components without coupling them together ๐
๐ In this section: Function-as-children pattern โข MouseTracker example โข Decoupled rendering โข Reusable behavior sharing
// โ WRONG: Tightly coupled, not reusablefunction MouseTracker() {const [position, setPosition] = useState({ x: 0, y: 0 });useEffect(() => {const handleMouseMove = (e) => setPosition({ x: e.clientX, y: e.clientY });window.addEventListener('mousemove', handleMouseMove);return () => window.removeEventListener('mousemove', handleMouseMove);}, []);return (<div><p>Mouse at: {position.x}, {position.y}</p></div>);}function App() {return <MouseTracker />;}export default App;
// โ GOOD: Render prop pattern - flexible and reusablefunction MouseTracker({ children }) {const [position, setPosition] = useState({ x: 0, y: 0 });useEffect(() => {const handleMouseMove = (e) => setPosition({ x: e.clientX, y: e.clientY });window.addEventListener('mousemove', handleMouseMove);return () => window.removeEventListener('mousemove', handleMouseMove);}, []);return children(position);}function App() {return (<div><MouseTracker>{({ x, y }) => <p>Mouse at: {x}, {y}</p>}</MouseTracker></div>);}export default App;
๐ง Hydration
React 18 changed the game with Selective Hydration! ๐ฎ Instead of hydrating your entire app at once (blocking everything), React can now hydrate components in priority order. Users click a button? That component gets hydrated FIRST! It's like a VIP line for interactivity! ๐ช
๐ Impact: HIGH โ This is how Netflix and Meta keep their apps feeling instant. Streaming SSR + selective hydration = blazing fast perceived performance! ๐
๐ In this section: Traditional SSR waterfall โข Streaming HTML โข Suspense boundaries โข Priority-based hydration
Wrap heavy or data-dependent components in Suspense so the rest of the page becomes interactive sooner.
Visualizing Selective Hydration Priority
// โ TRADITIONAL SSR: Blocking "Waterfall"// server.jsimport { renderToString } from 'react-dom/server';// 1. Wait for ALL data to be ready// 2. Render ENTIRE tree to HTMLconst html = renderToString(<App />);// 3. Send HTML to clientres.send(html);// 4. Client waits for ALL JS to load// 5. React hydrates ENTIRE tree at once// ๐ Result: Page is non-interactive until everything loads
// โ SELECTIVE HYDRATION (React 18+)// server.jsimport { pipeToNodeWritable } from 'react-dom/server';// 1. Start streaming HTML immediatelyconst { startWriting } = pipeToNodeWritable(<DataProvider data={data}><App /></DataProvider>,res,{onReadyToStream() {res.setHeader('Content-type', 'text/html');res.write('<!DOCTYPE html>');startWriting();}});// App.js// 2. Wrap slow components in Suspense<Suspense fallback={<Spinner />}><Comments /> {/* Large data fetching component */}</Suspense><Suspense fallback={<Spinner />}><Sidebar /></Suspense>// ๐ฏ Result:// - HTML streams in chunks// - User sees content sooner (First Contentful Paint)// - React hydrates interactive parts (like Sidebar) FIRST// - "Heavy" parts (Comments) hydrate later without blocking