JavaScript and React course ยท Module 8: Advanced React Patterns

Main Project: Building a Production Application

7 min read
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 component

Each 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:

  1. Performance optimization - use React.memo, useMemo, and useCallback where needed
  2. Code splitting - split the application into smaller chunks using lazy() and Suspense
  3. Environment variables - store configuration in .env files
  4. Error boundaries - add error handling at the component level
  5. Production build - run npm run build and 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?

Useful articles