JavaScript and React course ยท Module 8: Advanced React Patterns
Main Project: Building a Production Application
In this lesson7
You already know many separate tools but the real mission begins when you have to assemble them into one ship. Each pattern on its own looks simple. The trouble starts in a large application: where to keep the logic, how to share it between screens, and how to check that nothing broke after a change. This lesson answers exactly those questions.
The culminating moment of our space expedition has arrived. Throughout this module, we've explored advanced React patterns and best practices - from custom hooks, through Higher-Order Components and render props, to compound components, testing, and deployment. Now it's time to combine all these elements into one cohesive project: Space Mission Command Center.
Project architecture
When building a production application, it's crucial to think through its architecture. Our project combines the patterns we've learned in this module. Let's start with the deck map, meaning the folder structure:
1// Project structure
2// src/
3// hooks/ - Custom hooks (useMission, useFuel, useCrewStatus)
4// components/ - Reusable components
5// hoc/ - Higher-Order Components (withAuth, withLogging)
6// context/ - React Contexts (MissionContext, ThemeContext)
7// utils/ - Utility functions and validation
8// App.jsx - Main application componentEach folder is a separate deck of the ship with a clearly defined job. React itself doesn't impose this split, it is our convention. What matters is that the whole team sticks to one.
Custom Hooks - the heart of business logic
Custom hooks allow you to extract logic from components and make it reusable. In our project, every aspect of the space mission has its own dedicated hook. The useMission hook keeps the mission data and its status in state and exposes functions that change the status:
1// Custom hook for mission management
2function useMission(initialMission) {
3 const [mission, setMission] = useState(initialMission);
4 const [status, setStatus] = useState('planning');
5
6 const launch = () => {
7 if (mission.fuel >= mission.requiredFuel) {
8 setStatus('in-progress');
9 }
10 };
11
12 const complete = () => setStatus('completed');
13 const abort = () => setStatus('aborted');
14
15 return { mission, status, launch, complete, abort };
16}A component that uses useMission gets ready-made launch, complete and abort without knowing anything about the useState inside. Launching changes the status only when there is at least as much fuel as the mission requires. Otherwise the status stays planning.
Higher-Order Components and Render Props
HOCs allow you to add functionality to components without modifying their source code. A Higher-Order Component is a function that takes a component and returns a new component with extra props. Here withLogging attaches a logAction function:
1// HOC - adds action logging
2function withLogging(WrappedComponent) {
3 return function LoggedComponent(props) {
4 const logAction = (action) => {
5 console.log(`[LOG] ${new Date().toISOString()}: ${action}`);
6 };
7 return <WrappedComponent {...props} logAction={logAction} />;
8 };
9}The wrapped component works as before and only receives one new prop. Its own code doesn't change.
Render props provide flexibility in rendering. A component with a render prop keeps the data itself and hands over what to display to the function passed in the render prop:
1// Render Props - provides fuel data
2function FuelProvider({ render }) {
3 const [fuel, setFuel] = useState(100);
4 const consume = (amount) => setFuel(prev => Math.max(0, prev - amount));
5 const refuel = (amount) => setFuel(prev => Math.min(100, prev + amount));
6 return render({ fuel, consume, refuel });
7}FuelProvider makes sure the fuel never drops below 0 or exceeds 100, but it doesn't decide on the look. Since hooks were introduced, custom hooks have taken over most of the jobs of HOCs and render props in new code. Both patterns are still worth knowing, because you will meet them in older libraries and projects.
Compound Components
The compound components pattern allows you to create components that work together as a cohesive system. First the parent, which holds the shared expanded state and passes it to its children:
1// Compound Component - Mission Panel
2function MissionPanel({ children }) {
3 const [isExpanded, setIsExpanded] = useState(false);
4 return (
5 <div className="mission-panel">
6 {React.Children.map(children, child =>
7 React.isValidElement(child)
8 ? React.cloneElement(child, { isExpanded, setIsExpanded })
9 : child
10 )}
11 </div>
12 );
13}React.Children.map walks over the children, and React.cloneElement adds the isExpanded and setIsExpanded props to each one. Now the panel's parts, attached as properties of MissionPanel:
1MissionPanel.Header = function Header({ isExpanded, setIsExpanded, children }) {
2 return (
3 <div onClick={() => setIsExpanded(!isExpanded)} style={{ cursor: 'pointer' }}>
4 {children} {isExpanded ? 'โฒ' : 'โผ'}
5 </div>
6 );
7};
8
9MissionPanel.Body = function Body({ isExpanded, children }) {
10 return isExpanded ? <div>{children}</div> : null;
11};The header toggles the state, and the body shows up only when expanded. A practical note: react.dev counts cloneElement among the rarely used APIs, because it breaks easily, for example when you wrap a child in an extra div. In new code I recommend passing the shared state through context (createContext and useContext) and treating cloneElement as the historical version.
Testing - the key to reliability
Production code must be tested. Every custom hook, component, and utility function should have appropriate tests. We test hooks with the renderHook function and wrap state-changing calls in act. Both functions come from the @testing-library/react package (the former separate package @testing-library/react-hooks is deprecated):
1// Example custom hook test
2// describe('useMission', () => {
3// it('should launch the mission when there is enough fuel', () => {
4// const { result } = renderHook(() =>
5// useMission({ fuel: 100, requiredFuel: 50 })
6// );
7// act(() => result.current.launch());
8// expect(result.current.status).toBe('in-progress');
9// });
10//
11// it('should not launch the mission without fuel', () => {
12// const { result } = renderHook(() =>
13// useMission({ fuel: 20, requiredFuel: 50 })
14// );
15// act(() => result.current.launch());
16// expect(result.current.status).toBe('planning');
17// });
18// });The first test checks that with 100 units of fuel the mission launches, the second that with 20 the status stays planning. We ran both scenarios and they give exactly these results. The hook's code didn't have to change to be testable.
Deployment and optimization
Before deploying the application to production, make sure that:
- Performance optimization - use
React.memo,useMemo, anduseCallbackwhere needed - Code splitting - split the application into smaller chunks using
lazy()andSuspense - Environment variables - store configuration in
.envfiles - Error boundaries - add error handling at the component level
- Production build - run
npm run buildand test the result
Three clarifications to this list. In a Vite project only variables prefixed with VITE_ reach the browser code, read through import.meta.env, so never keep secrets in them. An error boundary is still written as a class component or comes from the react-error-boundary library. And you can view the build output locally with the npm run preview command.
Module summary
In this module, you learned advanced React patterns that are the foundation of professional application development:
- Custom Hooks - extracting and sharing logic between components
- Higher-Order Components - adding functionality through composition
- Render Props - flexible rendering with data passing
- Compound Components - creating cohesive component systems
- Testing - ensuring code reliability
- Deployment - deploying applications to production
My final advice: start with a custom hook, reach for HOCs and render props only when a library requires it, and write a test for every piece of logic that decides the fate of the mission. After this lesson a mentor project awaits you, in which you will assemble all these patterns into one laboratory.
These patterns are your cosmic engineer toolkit - with them, you can build React applications at the highest level!
Code for this lesson: App.jsx
1import React, { useState, useCallback, memo } from 'react';
2
3// =============================================
4// SPACE MISSION COMMAND CENTER
5// A project combining: custom hooks, HOC, render props,
6// compound components, and React best practices
7// =============================================
8
9// --- Custom Hook: useMission ---
10function useMission(initialName = '') {
11 const [mission, setMission] = useState({
12 name: initialName,
13 status: 'planning',
14 fuel: 100,
15 crew: 0,
16 });
17
18 const launch = useCallback(() => {
19 setMission(prev => {
20 if (prev.fuel < 30) return prev;
21 return { ...prev, status: 'in-progress' };
22 });
23 }, []);
24
25 const complete = useCallback(() => {
26 setMission(prev => ({ ...prev, status: 'completed' }));
27 }, []);
28
29 const abort = useCallback(() => {
30 setMission(prev => ({ ...prev, status: 'aborted' }));
31 }, []);
32
33 const updateFuel = useCallback((amount) => {
34 setMission(prev => ({
35 ...prev,
36 fuel: Math.max(0, Math.min(100, prev.fuel + amount)),
37 }));
38 }, []);
39
40 const updateCrew = useCallback((count) => {
41 setMission(prev => ({ ...prev, crew: Math.max(0, count) }));
42 }, []);
43
44 const rename = useCallback((name) => {
45 setMission(prev => ({ ...prev, name }));
46 }, []);
47
48 return { mission, launch, complete, abort, updateFuel, updateCrew, rename };
49}
50
51// --- HOC: withStatusColor ---
52function withStatusColor(WrappedComponent) {
53 return function StatusColorComponent(props) {
54 const colorMap = {
55 planning: '#00d4ff',
56 'in-progress': '#ffd700',
57 completed: '#4CAF50',
58 aborted: '#f44336',
59 };
60 const statusColor = colorMap[props.status] || '#e0e1dd';
61 return <WrappedComponent {...props} statusColor={statusColor} />;
62 };
63}
64
65// --- Compound Component: MissionPanel ---
66function MissionPanel({ children }) {
67 const [isExpanded, setIsExpanded] = useState(true);
68 return (
69 <div style={{
70 background: '#1b2838', borderRadius: '12px',
71 padding: '16px', marginBottom: '12px',
72 border: '1px solid #2a3f5f',
73 }}>
74 {React.Children.map(children, child =>
75 React.isValidElement(child)
76 ? React.cloneElement(child, { isExpanded, setIsExpanded })
77 : child
78 )}
79 </div>
80 );
81}
82
83MissionPanel.Header = function Header({ isExpanded, setIsExpanded, children }) {
84 return (
85 <div
86 onClick={() => setIsExpanded(!isExpanded)}
87 style={{
88 cursor: 'pointer', display: 'flex',
89 justifyContent: 'space-between', alignItems: 'center',
90 color: '#ffd700', fontSize: '16px', fontWeight: 'bold',
91 }}
92 >
93 {children}
94 <span>{isExpanded ? 'โฒ' : 'โผ'}</span>
95 </div>
96 );
97};
98
99MissionPanel.Body = function Body({ isExpanded, children }) {
100 if (!isExpanded) return null;
101 return <div style={{ marginTop: '12px' }}>{children}</div>;
102};
103
104// --- Render Props: FuelGauge ---
105function FuelGauge({ fuel, render }) {
106 const level = fuel > 60 ? 'high' : fuel > 30 ? 'medium' : 'low';
107 const color = level === 'high' ? '#4CAF50' : level === 'medium' ? '#ffd700' : '#f44336';
108 return render({ fuel, level, color });
109}
110
111// --- Component with HOC ---
112const StatusBadge = withStatusColor(memo(function StatusBadge({ status, statusColor }) {
113 return (
114 <span style={{
115 display: 'inline-block', padding: '4px 12px',
116 borderRadius: '12px', background: statusColor,
117 color: '#0d1b2a', fontWeight: 'bold', fontSize: '13px',
118 }}>
119 {status.toUpperCase()}
120 </span>
121 );
122}));
123
124// --- Main App component ---
125export default function App() {
126 const { mission, launch, complete, abort, updateFuel, updateCrew, rename } = useMission('Apollo X');
127
128 return (
129 <div style={{
130 background: '#0d1b2a', minHeight: '100vh',
131 padding: '20px', fontFamily: 'Arial', color: '#e0e1dd',
132 }}>
133 <h1 style={{ color: '#ffd700', textAlign: 'center', marginBottom: '6px' }}>
134 Command Center
135 </h1>
136 <p style={{ textAlign: 'center', color: '#778da9', marginBottom: '20px', fontSize: '14px' }}>
137 A project combining custom hooks, HOC, render props, and compound components
138 </p>
139
140 {/* Mission panel - Compound Component */}
141 <MissionPanel>
142 <MissionPanel.Header>Mission Data</MissionPanel.Header>
143 <MissionPanel.Body>
144 <div style={{ marginBottom: '10px' }}>
145 <label style={{ color: '#778da9', fontSize: '13px' }}>Mission name:</label>
146 <input
147 value={mission.name}
148 onChange={e => rename(e.target.value)}
149 style={{
150 width: '100%', padding: '6px 10px', marginTop: '4px',
151 background: '#0d1b2a', border: '1px solid #2a3f5f',
152 borderRadius: '6px', color: '#e0e1dd', fontSize: '14px',
153 }}
154 />
155 </div>
156 <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '10px' }}>
157 <span style={{ color: '#778da9', fontSize: '13px' }}>Status:</span>
158 <StatusBadge status={mission.status} />
159 </div>
160 <div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
161 <button onClick={launch} style={btnStyle('#4CAF50')}>Start</button>
162 <button onClick={complete} style={btnStyle('#2196F3')}>Finish</button>
163 <button onClick={abort} style={btnStyle('#f44336')}>Abort</button>
164 </div>
165 </MissionPanel.Body>
166 </MissionPanel>
167
168 {/* Fuel - Render Props */}
169 <MissionPanel>
170 <MissionPanel.Header>Fuel</MissionPanel.Header>
171 <MissionPanel.Body>
172 <FuelGauge fuel={mission.fuel} render={({ fuel, color }) => (
173 <div>
174 <div style={{
175 width: '100%', height: '20px', background: '#0d1b2a',
176 borderRadius: '10px', overflow: 'hidden', marginBottom: '8px',
177 }}>
178 <div style={{
179 width: fuel + '%', height: '100%',
180 background: color, borderRadius: '10px',
181 transition: 'all 0.3s',
182 }} />
183 </div>
184 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
185 <span style={{ color, fontWeight: 'bold' }}>{fuel}%</span>
186 <div style={{ display: 'flex', gap: '6px' }}>
187 <button onClick={() => updateFuel(-10)} style={btnStyle('#f44336')}>-10</button>
188 <button onClick={() => updateFuel(10)} style={btnStyle('#4CAF50')}>+10</button>
189 </div>
190 </div>
191 </div>
192 )} />
193 </MissionPanel.Body>
194 </MissionPanel>
195
196 {/* Crew */}
197 <MissionPanel>
198 <MissionPanel.Header>Crew ({mission.crew} members)</MissionPanel.Header>
199 <MissionPanel.Body>
200 <div style={{ display: 'flex', gap: '6px' }}>
201 <button onClick={() => updateCrew(mission.crew + 1)} style={btnStyle('#4CAF50')}>
202 + Add
203 </button>
204 <button onClick={() => updateCrew(mission.crew - 1)} style={btnStyle('#f44336')}>
205 - Remove
206 </button>
207 </div>
208 </MissionPanel.Body>
209 </MissionPanel>
210 </div>
211 );
212}
213
214function btnStyle(bg) {
215 return {
216 padding: '6px 14px', background: bg, color: 'white',
217 border: 'none', borderRadius: '6px', cursor: 'pointer',
218 fontWeight: 'bold', fontSize: '13px',
219 };
220}Spotted a mistake in this lesson?