Master advanced React patterns for building flexible, reusable components. Learn composition over inheritance, render props, compound components, and HOCs.
Component composition is React's superpower for building flexible, maintainable applications. Instead of using class inheritance like traditional object-oriented programming, React embraces composition - combining simple components to create complex functionality.
Think of composition like building with LEGO blocks. Instead of creating one giant, complex piece, you combine small, focused pieces to build anything you can imagine. This approach makes your components more reusable, testable, and easier to understand.
In this advanced lesson, you'll master the composition patterns that separate professional React developers from beginners - from basic children props to sophisticated render props, compound components, and higher-order components.
React strongly favors composition over inheritance, and for good reason. This philosophy leads to more flexible and maintainable code.
Inheritance vs Composition:
Inheritance Problems:
Composition Benefits:
The Children Prop - Your First Composition Tool:
The simplest form of composition uses React's built-in children prop:
1// Basic composition with children
2function Card({ children }) {
3 return (
4 <div className="card">
5 <div className="card-content">
6 {children}
7 </div>
8 </div>
9 );
10}
11
12// Usage
13function App() {
14 return (
15 <Card>
16 <h2>Welcome!</h2>
17 <p>This content is composed inside the Card component.</p>
18 <button>Click me</button>
19 </Card>
20 );
21}Advanced Children Patterns:
1// Multiple children slots
2function Layout({ header, sidebar, children, footer }) {
3 return (
4 <div className="layout">
5 <header className="header">{header}</header>
6 <div className="main">
7 <aside className="sidebar">{sidebar}</aside>
8 <main className="content">{children}</main>
9 </div>
10 <footer className="footer">{footer}</footer>
11 </div>
12 );
13}
14
15// Usage with named slots
16function App() {
17 return (
18 <Layout
19 header={<h1>My App</h1>}
20 sidebar={<nav>Navigation here</nav>}
21 footer={<p>© 2024 My Company</p>}
22 >
23 <article>
24 <h2>Main Content</h2>
25 <p>This goes in the main content area.</p>
26 </article>
27 </Layout>
28 );
29}Children Manipulation:
1import { Children, cloneElement } from 'react';
2
3function ButtonGroup({ children, size = 'medium' }) {
4 return (
5 <div className="button-group">
6 {Children.map(children, (child, index) => {
7 // Clone each child and add additional props
8 return cloneElement(child, {
9 key: index,
10 size: child.props.size || size,
11 className: `btn-group-item ${child.props.className || ''}`
12 });
13 })}
14 </div>
15 );
16}
17
18// Usage
19function App() {
20 return (
21 <ButtonGroup size="large">
22 <button>First</button>
23 <button>Second</button>
24 <button size="small">Third (overrides size)</button>
25 </ButtonGroup>
26 );
27}The render props pattern provides maximum flexibility by allowing components to share logic while letting the consumer control rendering.
What are Render Props?
A render prop is a function prop that a component uses to know what to render:
1// Mouse tracker with render prop
2function MouseTracker({ render }) {
3 const [position, setPosition] = useState({ x: 0, y: 0 });
4
5 useEffect(() => {
6 const handleMouseMove = (event) => {
7 setPosition({ x: event.clientX, y: event.clientY });
8 };
9
10 window.addEventListener('mousemove', handleMouseMove);
11 return () => window.removeEventListener('mousemove', handleMouseMove);
12 }, []);
13
14 return render(position);
15}
16
17// Usage 1: Display coordinates
18function App() {
19 return (
20 <MouseTracker
21 render={({ x, y }) => (
22 <div>
23 <h2>Mouse Position</h2>
24 <p>The mouse is at ({x}, {y})</p>
25 </div>
26 )}
27 />
28 );
29}
30
31// Usage 2: Follow cursor with element
32function CursorFollower() {
33 return (
34 <MouseTracker
35 render={({ x, y }) => (
36 <div
37 style={{
38 position: 'fixed',
39 left: x,
40 top: y,
41 transform: 'translate(-50%, -50%)',
42 pointerEvents: 'none'
43 }}
44 >
45 ๐ป
46 </div>
47 )}
48 />
49 );
50}Data Fetcher with Render Props:
1function DataFetcher({ url, render }) {
2 const [data, setData] = useState(null);
3 const [loading, setLoading] = useState(true);
4 const [error, setError] = useState(null);
5
6 useEffect(() => {
7 async function fetchData() {
8 try {
9 setLoading(true);
10 const response = await fetch(url);
11 if (!response.ok) throw new Error('Failed to fetch');
12
13 const result = await response.json();
14 setData(result);
15 setError(null);
16 } catch (err) {
17 setError(err.message);
18 setData(null);
19 } finally {
20 setLoading(false);
21 }
22 }
23
24 fetchData();
25 }, [url]);
26
27 return render({ data, loading, error });
28}
29
30// Usage for different data types
31function UserProfile({ userId }) {
32 return (
33 <DataFetcher
34 url={`/api/users/${userId}`}
35 render={({ data, loading, error }) => {
36 if (loading) return <div>Loading user...</div>;
37 if (error) return <div>Error: {error}</div>;
38
39 return (
40 <div className="user-profile">
41 <img src={data.avatar} alt={data.name} />
42 <h2>{data.name}</h2>
43 <p>{data.email}</p>
44 </div>
45 );
46 }}
47 />
48 );
49}
50
51function PostList() {
52 return (
53 <DataFetcher
54 url="/api/posts"
55 render={({ data, loading, error }) => {
56 if (loading) return <div>Loading posts...</div>;
57 if (error) return <div>Error: {error}</div>;
58
59 return (
60 <div className="post-list">
61 {data.map(post => (
62 <article key={post.id}>
63 <h3>{post.title}</h3>
64 <p>{post.excerpt}</p>
65 </article>
66 ))}
67 </div>
68 );
69 }}
70 />
71 );
72}Function as Children Pattern:
This is a variant of render props where the children prop is a function:
1function Toggle({ children }) {
2 const [on, setOn] = useState(false);
3
4 const toggle = () => setOn(prev => !prev);
5 const turnOn = () => setOn(true);
6 const turnOff = () => setOn(false);
7
8 return children({ on, toggle, turnOn, turnOff });
9}
10
11// Usage
12function App() {
13 return (
14 <Toggle>
15 {({ on, toggle, turnOn, turnOff }) => (
16 <div>
17 <p>The toggle is {on ? 'ON' : 'OFF'}</p>
18 <button onClick={toggle}>Toggle</button>
19 <button onClick={turnOn}>Turn On</button>
20 <button onClick={turnOff}>Turn Off</button>
21
22 {on && (
23 <div className="content">
24 <p>This content appears when toggle is ON!</p>
25 </div>
26 )}
27 </div>
28 )}
29 </Toggle>
30 );
31}Compound components work together as a unit, providing a clean API for complex UI patterns. Think of HTML's <select> and <option> elements.
Basic Compound Component:
1// Accordion compound component
2const AccordionContext = createContext();
3
4function Accordion({ children, allowMultiple = false }) {
5 const [openItems, setOpenItems] = useState(new Set());
6
7 const toggle = (index) => {
8 setOpenItems(prev => {
9 const newSet = new Set(prev);
10
11 if (newSet.has(index)) {
12 newSet.delete(index);
13 } else {
14 if (!allowMultiple) {
15 newSet.clear();
16 }
17 newSet.add(index);
18 }
19
20 return newSet;
21 });
22 };
23
24 const isOpen = (index) => openItems.has(index);
25
26 return (
27 <AccordionContext.Provider value={{ toggle, isOpen }}>
28 <div className="accordion">
29 {children}
30 </div>
31 </AccordionContext.Provider>
32 );
33}
34
35function AccordionItem({ children, index }) {
36 return (
37 <div className="accordion-item">
38 {children}
39 </div>
40 );
41}
42
43function AccordionHeader({ children, index }) {
44 const { toggle, isOpen } = useContext(AccordionContext);
45
46 return (
47 <button
48 className={`accordion-header ${isOpen(index) ? 'open' : ''}`}
49 onClick={() => toggle(index)}
50 >
51 {children}
52 <span className="accordion-icon">
53 {isOpen(index) ? 'โ' : '+'}
54 </span>
55 </button>
56 );
57}
58
59function AccordionPanel({ children, index }) {
60 const { isOpen } = useContext(AccordionContext);
61
62 return (
63 <div className={`accordion-panel ${isOpen(index) ? 'open' : ''}`}>
64 {isOpen(index) && (
65 <div className="accordion-content">
66 {children}
67 </div>
68 )}
69 </div>
70 );
71}
72
73// Attach sub-components to main component
74Accordion.Item = AccordionItem;
75Accordion.Header = AccordionHeader;
76Accordion.Panel = AccordionPanel;Usage of Compound Accordion:
1function FAQ() {
2 return (
3 <Accordion allowMultiple={true}>
4 <Accordion.Item index={0}>
5 <Accordion.Header index={0}>
6 What is React?
7 </Accordion.Header>
8 <Accordion.Panel index={0}>
9 <p>React is a JavaScript library for building user interfaces...</p>
10 </Accordion.Panel>
11 </Accordion.Item>
12
13 <Accordion.Item index={1}>
14 <Accordion.Header index={1}>
15 How do hooks work?
16 </Accordion.Header>
17 <Accordion.Panel index={1}>
18 <p>Hooks are functions that let you use state and other React features...</p>
19 </Accordion.Panel>
20 </Accordion.Item>
21
22 <Accordion.Item index={2}>
23 <Accordion.Header index={2}>
24 What is JSX?
25 </Accordion.Header>
26 <Accordion.Panel index={2}>
27 <p>JSX is a syntax extension for JavaScript...</p>
28 </Accordion.Panel>
29 </Accordion.Item>
30 </Accordion>
31 );
32}Advanced Compound Component - Modal:
1const ModalContext = createContext();
2
3function Modal({ children, isOpen, onClose }) {
4 useEffect(() => {
5 const handleEscape = (event) => {
6 if (event.key === 'Escape') {
7 onClose();
8 }
9 };
10
11 if (isOpen) {
12 document.addEventListener('keydown', handleEscape);
13 document.body.style.overflow = 'hidden';
14 }
15
16 return () => {
17 document.removeEventListener('keydown', handleEscape);
18 document.body.style.overflow = 'unset';
19 };
20 }, [isOpen, onClose]);
21
22 if (!isOpen) return null;
23
24 return (
25 <ModalContext.Provider value={{ onClose }}>
26 <div className="modal-overlay" onClick={onClose}>
27 <div className="modal-container" onClick={e => e.stopPropagation()}>
28 {children}
29 </div>
30 </div>
31 </ModalContext.Provider>
32 );
33}
34
35function ModalHeader({ children }) {
36 const { onClose } = useContext(ModalContext);
37
38 return (
39 <div className="modal-header">
40 <div className="modal-title">
41 {children}
42 </div>
43 <button className="modal-close" onClick={onClose}>
44 ร
45 </button>
46 </div>
47 );
48}
49
50function ModalBody({ children }) {
51 return (
52 <div className="modal-body">
53 {children}
54 </div>
55 );
56}
57
58function ModalFooter({ children }) {
59 return (
60 <div className="modal-footer">
61 {children}
62 </div>
63 );
64}
65
66// Attach sub-components
67Modal.Header = ModalHeader;
68Modal.Body = ModalBody;
69Modal.Footer = ModalFooter;
70
71// Usage
72function App() {
73 const [isModalOpen, setModalOpen] = useState(false);
74
75 return (
76 <div>
77 <button onClick={() => setModalOpen(true)}>
78 Open Modal
79 </button>
80
81 <Modal isOpen={isModalOpen} onClose={() => setModalOpen(false)}>
82 <Modal.Header>
83 <h2>Confirm Action</h2>
84 </Modal.Header>
85
86 <Modal.Body>
87 <p>Are you sure you want to delete this item?</p>
88 </Modal.Body>
89
90 <Modal.Footer>
91 <button onClick={() => setModalOpen(false)}>
92 Cancel
93 </button>
94 <button onClick={() => {
95 // Handle delete
96 setModalOpen(false);
97 }}>
98 Delete
99 </button>
100 </Modal.Footer>
101 </Modal>
102 </div>
103 );
104}Higher-Order Components are functions that take a component and return a new component with additional functionality. They're perfect for cross-cutting concerns.
Basic HOC Pattern:
1// HOC for loading states
2function withLoading(WrappedComponent) {
3 return function WithLoadingComponent(props) {
4 if (props.isLoading) {
5 return (
6 <div className="loading-spinner">
7 <div className="spinner"></div>
8 <p>Loading...</p>
9 </div>
10 );
11 }
12
13 return <WrappedComponent {...props} />;
14 };
15}
16
17// Usage
18const UserProfile = ({ user }) => (
19 <div>
20 <h2>{user.name}</h2>
21 <p>{user.email}</p>
22 </div>
23);
24
25const UserProfileWithLoading = withLoading(UserProfile);
26
27// In parent component
28function App() {
29 const [user, setUser] = useState(null);
30 const [isLoading, setIsLoading] = useState(true);
31
32 useEffect(() => {
33 fetchUser().then(userData => {
34 setUser(userData);
35 setIsLoading(false);
36 });
37 }, []);
38
39 return (
40 <UserProfileWithLoading
41 user={user}
42 isLoading={isLoading}
43 />
44 );
45}Authentication HOC:
1function withAuth(WrappedComponent, { redirectTo = '/login' } = {}) {
2 return function WithAuthComponent(props) {
3 const [user, setUser] = useState(null);
4 const [loading, setLoading] = useState(true);
5
6 useEffect(() => {
7 // Check authentication status
8 checkAuth()
9 .then(userData => {
10 setUser(userData);
11 setLoading(false);
12 })
13 .catch(() => {
14 setLoading(false);
15 // Redirect to login
16 window.location.href = redirectTo;
17 });
18 }, []);
19
20 if (loading) {
21 return <div>Checking authentication...</div>;
22 }
23
24 if (!user) {
25 return null; // Will redirect
26 }
27
28 return <WrappedComponent {...props} user={user} />;
29 };
30}
31
32// Usage
33const Dashboard = ({ user }) => (
34 <div>
35 <h1>Welcome back, {user.name}!</h1>
36 <p>Your dashboard content here...</p>
37 </div>
38);
39
40const ProtectedDashboard = withAuth(Dashboard);Data Fetching HOC:
1function withData(url, propName = 'data') {
2 return function(WrappedComponent) {
3 return function WithDataComponent(props) {
4 const [data, setData] = useState(null);
5 const [loading, setLoading] = useState(true);
6 const [error, setError] = useState(null);
7
8 useEffect(() => {
9 async function fetchData() {
10 try {
11 setLoading(true);
12 const response = await fetch(url);
13 if (!response.ok) throw new Error('Failed to fetch');
14
15 const result = await response.json();
16 setData(result);
17 } catch (err) {
18 setError(err.message);
19 } finally {
20 setLoading(false);
21 }
22 }
23
24 fetchData();
25 }, []);
26
27 const enhancedProps = {
28 ...props,
29 [propName]: data,
30 [`${propName}Loading`]: loading,
31 [`${propName}Error`]: error
32 };
33
34 return <WrappedComponent {...enhancedProps} />;
35 };
36 };
37}
38
39// Usage
40const PostList = ({ posts, postsLoading, postsError }) => {
41 if (postsLoading) return <div>Loading posts...</div>;
42 if (postsError) return <div>Error: {postsError}</div>;
43
44 return (
45 <div>
46 {posts.map(post => (
47 <article key={post.id}>
48 <h3>{post.title}</h3>
49 <p>{post.content}</p>
50 </article>
51 ))}
52 </div>
53 );
54};
55
56const PostListWithData = withData('/api/posts', 'posts')(PostList);Composing Multiple HOCs:
1// Compose multiple HOCs
2const enhance = (WrappedComponent) =>
3 withAuth(
4 withLoading(
5 withData('/api/user-posts', 'userPosts')(
6 WrappedComponent
7 )
8 )
9 );
10
11const UserPosts = ({ userPosts, user }) => (
12 <div>
13 <h2>{user.name}'s Posts</h2>
14 {userPosts.map(post => (
15 <article key={post.id}>
16 <h3>{post.title}</h3>
17 </article>
18 ))}
19 </div>
20);
21
22const EnhancedUserPosts = enhance(UserPosts);HOC Best Practices:
{...props}displayName for debugging: WithLoading.displayName = withLoading(${WrappedComponent.displayName});Build a comprehensive component system demonstrating all composition patterns: children props, render props, compound components, and HOCs working together.
1// Complete Component Composition System
2import React, { useState, useEffect, useContext, createContext, Children, cloneElement } from 'react';
3
4// 1. Higher-Order Components
5function withLoading(WrappedComponent) {
6 const WithLoading = (props) => {
7 if (props.isLoading) {
8 return (
9 <div className="flex items-center justify-center p-8">
10 <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
11 <span className="ml-2">Loading...</span>
12 </div>
13 );
14 }
15 return <WrappedComponent {...props} />;
16 };
17
18 WithLoading.displayName = `withLoading(${WrappedComponent.displayName || WrappedComponent.name})`;
19 return WithLoading;
20}
21
22function withAuth(WrappedComponent) {
23 const WithAuth = (props) => {
24 const [user, setUser] = useState(null);
25 const [loading, setLoading] = useState(true);
26
27 useEffect(() => {
28 // Simulate auth check
29 setTimeout(() => {
30 setUser({ name: 'John Doe', role: 'admin' });
31 setLoading(false);
32 }, 1000);
33 }, []);
34
35 if (loading) {
36 return <div>Checking authentication...</div>;
37 }
38
39 if (!user) {
40 return <div className="p-4 bg-red-50 text-red-700">Please log in to access this content.</div>;
41 }
42
43 return <WrappedComponent {...props} user={user} />;
44 };
45
46 WithAuth.displayName = `withAuth(${WrappedComponent.displayName || WrappedComponent.name})`;
47 return WithAuth;
48}
49
50// 2. Render Props Components
51function DataFetcher({ url, children }) {
52 const [data, setData] = useState(null);
53 const [loading, setLoading] = useState(true);
54 const [error, setError] = useState(null);
55
56 useEffect(() => {
57 async function fetchData() {
58 try {
59 setLoading(true);
60 // Simulate API call
61 await new Promise(resolve => setTimeout(resolve, 1500));
62
63 const mockData = {
64 '/api/posts': [
65 { id: 1, title: 'Understanding React Composition', author: 'Jane Smith', likes: 42 },
66 { id: 2, title: 'Advanced Component Patterns', author: 'Bob Johnson', likes: 38 },
67 { id: 3, title: 'Building Reusable Components', author: 'Alice Brown', likes: 51 }
68 ],
69 '/api/users': [
70 { id: 1, name: 'Jane Smith', role: 'Author', avatar: '๐ฉโ๐ป' },
71 { id: 2, name: 'Bob Johnson', role: 'Editor', avatar: '๐จโ๐ผ' },
72 { id: 3, name: 'Alice Brown', role: 'Author', avatar: '๐ฉโ๐จ' }
73 ]
74 };
75
76 setData(mockData[url] || []);
77 } catch (err) {
78 setError(err.message);
79 } finally {
80 setLoading(false);
81 }
82 }
83
84 fetchData();
85 }, [url]);
86
87 return children({ data, loading, error });
88}
89
90function Toggle({ children }) {
91 const [on, setOn] = useState(false);
92
93 return children({
94 on,
95 toggle: () => setOn(prev => !prev),
96 turnOn: () => setOn(true),
97 turnOff: () => setOn(false)
98 });
99}
100
101// 3. Compound Components
102const TabContext = createContext();
103
104function Tabs({ children, defaultTab = 0 }) {
105 const [activeTab, setActiveTab] = useState(defaultTab);
106
107 return (
108 <TabContext.Provider value={{ activeTab, setActiveTab }}>
109 <div className="tabs-container">
110 {children}
111 </div>
112 </TabContext.Provider>
113 );
114}
115
116function TabList({ children }) {
117 return (
118 <div className="flex border-b border-gray-200 mb-4">
119 {Children.map(children, (child, index) =>
120 cloneElement(child, { index })
121 )}
122 </div>
123 );
124}
125
126function Tab({ children, index }) {
127 const { activeTab, setActiveTab } = useContext(TabContext);
128 const isActive = activeTab === index;
129
130 return (
131 <button
132 className={`px-4 py-2 font-medium text-sm transition-colors ${
133 isActive
134 ? 'border-b-2 border-blue-500 text-blue-600'
135 : 'text-gray-600 hover:text-gray-800'
136 }`}
137 onClick={() => setActiveTab(index)}
138 >
139 {children}
140 </button>
141 );
142}
143
144function TabPanels({ children }) {
145 const { activeTab } = useContext(TabContext);
146 return (
147 <div className="tab-panels">
148 {Children.toArray(children)[activeTab]}
149 </div>
150 );
151}
152
153function TabPanel({ children }) {
154 return <div className="tab-panel">{children}</div>;
155}
156
157// Attach compound component pieces
158Tabs.List = TabList;
159Tabs.Tab = Tab;
160Tabs.Panels = TabPanels;
161Tabs.Panel = TabPanel;
162
163// 4. Layout Components with Composition
164function Card({ children, className = '', ...props }) {
165 return (
166 <div className={`bg-white rounded-lg shadow-md overflow-hidden ${className}`} {...props}>
167 {children}
168 </div>
169 );
170}
171
172function CardHeader({ children, className = '' }) {
173 return (
174 <div className={`px-6 py-4 border-b border-gray-200 ${className}`}>
175 {children}
176 </div>
177 );
178}
179
180function CardBody({ children, className = '' }) {
181 return (
182 <div className={`px-6 py-4 ${className}`}>
183 {children}
184 </div>
185 );
186}
187
188function CardFooter({ children, className = '' }) {
189 return (
190 <div className={`px-6 py-4 border-t border-gray-200 bg-gray-50 ${className}`}>
191 {children}
192 </div>
193 );
194}
195
196Card.Header = CardHeader;
197Card.Body = CardBody;
198Card.Footer = CardFooter;
199
200// 5. Business Components Using All Patterns
201function PostList({ posts, isLoading, user }) {
202 return (
203 <Card>
204 <Card.Header>
205 <h2 className="text-xl font-bold">Latest Posts</h2>
206 <p className="text-gray-600">Welcome back, {user.name}!</p>
207 </Card.Header>
208
209 <Card.Body>
210 {posts && posts.length > 0 ? (
211 <div className="space-y-4">
212 {posts.map(post => (
213 <div key={post.id} className="border rounded-lg p-4">
214 <h3 className="font-semibold text-lg">{post.title}</h3>
215 <div className="flex items-center justify-between mt-2 text-sm text-gray-600">
216 <span>by {post.author}</span>
217 <span>โค๏ธ {post.likes} likes</span>
218 </div>
219 </div>
220 ))}
221 </div>
222 ) : (
223 <p>No posts available.</p>
224 )}
225 </Card.Body>
226 </Card>
227 );
228}
229
230function UserList({ users, isLoading }) {
231 return (
232 <Card>
233 <Card.Header>
234 <h2 className="text-xl font-bold">Team Members</h2>
235 </Card.Header>
236
237 <Card.Body>
238 {users && users.length > 0 ? (
239 <div className="space-y-3">
240 {users.map(user => (
241 <div key={user.id} className="flex items-center space-x-3">
242 <span className="text-2xl">{user.avatar}</span>
243 <div>
244 <p className="font-medium">{user.name}</p>
245 <p className="text-sm text-gray-600">{user.role}</p>
246 </div>
247 </div>
248 ))}
249 </div>
250 ) : (
251 <p>No users available.</p>
252 )}
253 </Card.Body>
254 </Card>
255 );
256}
257
258// Enhance components with HOCs
259const PostListWithAuth = withAuth(withLoading(PostList));
260const UserListWithLoading = withLoading(UserList);
261
262// 6. Main Dashboard Component
263function Dashboard() {
264 return (
265 <div className="max-w-6xl mx-auto p-6 space-y-6">
266 <h1 className="text-3xl font-bold mb-8">Component Composition Dashboard</h1>
267
268 {/* Tabs with Compound Components */}
269 <Tabs defaultTab={0}>
270 <Tabs.List>
271 <Tabs.Tab>Posts</Tabs.Tab>
272 <Tabs.Tab>Users</Tabs.Tab>
273 <Tabs.Tab>Settings</Tabs.Tab>
274 </Tabs.List>
275
276 <Tabs.Panels>
277 {/* Tab 1: Posts with Render Props + HOCs */}
278 <Tabs.Panel>
279 <DataFetcher url="/api/posts">
280 {({ data, loading, error }) => {
281 if (error) return <div className="text-red-500">Error: {error}</div>;
282 return <PostListWithAuth posts={data} isLoading={loading} />;
283 }}
284 </DataFetcher>
285 </Tabs.Panel>
286
287 {/* Tab 2: Users with Render Props + HOC */}
288 <Tabs.Panel>
289 <DataFetcher url="/api/users">
290 {({ data, loading, error }) => {
291 if (error) return <div className="text-red-500">Error: {error}</div>;
292 return <UserListWithLoading users={data} isLoading={loading} />;
293 }}
294 </DataFetcher>
295 </Tabs.Panel>
296
297 {/* Tab 3: Settings with Toggle Render Props */}
298 <Tabs.Panel>
299 <Card>
300 <Card.Header>
301 <h2 className="text-xl font-bold">Settings</h2>
302 </Card.Header>
303
304 <Card.Body className="space-y-4">
305 <Toggle>
306 {({ on: notifications, toggle: toggleNotifications }) => (
307 <div className="flex items-center justify-between">
308 <span>Email Notifications</span>
309 <button
310 onClick={toggleNotifications}
311 className={`px-4 py-2 rounded ${
312 notifications
313 ? 'bg-green-500 text-white'
314 : 'bg-gray-200 text-gray-700'
315 }`}
316 >
317 {notifications ? 'ON' : 'OFF'}
318 </button>
319 </div>
320 )}
321 </Toggle>
322
323 <Toggle>
324 {({ on: darkMode, toggle: toggleDarkMode }) => (
325 <div className="flex items-center justify-between">
326 <span>Dark Mode</span>
327 <button
328 onClick={toggleDarkMode}
329 className={`px-4 py-2 rounded ${
330 darkMode
331 ? 'bg-gray-800 text-white'
332 : 'bg-gray-200 text-gray-700'
333 }`}
334 >
335 {darkMode ? '๐' : 'โ๏ธ'}
336 </button>
337 </div>
338 )}
339 </Toggle>
340
341 <Toggle>
342 {({ on: autoSave, toggle: toggleAutoSave }) => (
343 <div className="flex items-center justify-between">
344 <span>Auto Save</span>
345 <button
346 onClick={toggleAutoSave}
347 className={`px-4 py-2 rounded ${
348 autoSave
349 ? 'bg-blue-500 text-white'
350 : 'bg-gray-200 text-gray-700'
351 }`}
352 >
353 {autoSave ? 'ENABLED' : 'DISABLED'}
354 </button>
355 </div>
356 )}
357 </Toggle>
358 </Card.Body>
359 </Card>
360 </Tabs.Panel>
361 </Tabs.Panels>
362 </Tabs>
363
364 {/* Composition Patterns Summary */}
365 <Card>
366 <Card.Header>
367 <h2 className="text-xl font-bold">๐ฏ Composition Patterns Demonstrated</h2>
368 </Card.Header>
369
370 <Card.Body>
371 <div className="grid md:grid-cols-2 gap-4 text-sm">
372 <div>
373 <h3 className="font-semibold mb-2">โ
Patterns Used:</h3>
374 <ul className="space-y-1 text-gray-700">
375 <li>๐ง Higher-Order Components (withAuth, withLoading)</li>
376 <li>๐ญ Render Props (DataFetcher, Toggle)</li>
377 <li>๐งฉ Compound Components (Tabs, Card)</li>
378 <li>๐ถ Children Props (Card sections)</li>
379 <li>๐ Context API (TabContext)</li>
380 <li>โก Function as Children (Toggle pattern)</li>
381 </ul>
382 </div>
383 <div>
384 <h3 className="font-semibold mb-2">๐ Benefits Achieved:</h3>
385 <ul className="space-y-1 text-gray-700">
386 <li>๐ Highly reusable components</li>
387 <li>๐งช Easy to test in isolation</li>
388 <li>๐ Clear separation of concerns</li>
389 <li>๐จ Flexible UI composition</li>
390 <li>๐ง Cross-cutting concerns handled cleanly</li>
391 <li>๐ฆ Modular and maintainable code</li>
392 </ul>
393 </div>
394 </div>
395 </Card.Body>
396 </Card>
397 </div>
398 );
399}
400
401// Main App Component
402function App() {
403 return (
404 <div className="min-h-screen bg-gray-100">
405 <Dashboard />
406 </div>
407 );
408}This comprehensive example demonstrates all major React composition patterns working together:
Higher-Order Components (HOCs):
withAuth: Handles authentication logic across componentswithLoading: Provides loading UI for any componentRender Props Pattern:
DataFetcher: Flexible data fetching with custom renderingToggle: Reusable toggle state logic with render flexibilityCompound Components:
Tabs: Complex tabbed interface with coordinated sub-componentsCard: Flexible card layout with semantic sectionsChildren Props & Composition:
Real-World Integration:
PostList, UserList) enhanced with HOCsArchitecture Benefits:
You've mastered React composition patterns! Next, you'll learn production best practices:
You'll discover how to build React applications that scale to enterprise level with professional development practices.
Your progress is saved in this browser, on this device only.