Skip to main content

Chapter 9: Advanced Patterns - Mastering React Design Patterns and Architecture

Welcome to the comprehensive guide to advanced React patterns! In this chapter, we'll explore sophisticated design patterns, architectural approaches, and advanced techniques that separate expert React developers from beginners.

Learning Objectives

By the end of this chapter, you will understand:

  • What advanced React patterns are and why they're essential for building scalable applications
  • How to implement compound components for flexible and reusable UI patterns
  • Why render props and higher-order components work and when to use each approach
  • What the provider pattern is and how to create flexible context providers
  • How to implement custom hooks patterns for complex state management and side effects
  • What performance patterns exist and how to optimize React applications at scale
  • Why architectural patterns matter and how to structure large React applications

What are Advanced React Patterns? The Foundation of Expert Development

What are Advanced React Patterns?

Advanced React patterns are sophisticated techniques and architectural approaches that enable you to build complex, maintainable, and scalable React applications. These patterns go beyond basic component creation to solve real-world problems in enterprise applications.

Advanced patterns are what transform your React code from simple components into a sophisticated, maintainable architecture that can handle complex business requirements and scale with your team.

What Makes Patterns "Advanced"?

Advanced patterns provide capabilities that basic React development cannot:

  1. Composition over Inheritance: Building complex UIs through component composition
  2. Separation of Concerns: Isolating different aspects of application logic
  3. Reusability: Creating flexible, configurable components
  4. Performance: Optimizing rendering and state management at scale
  5. Maintainability: Making code easier to understand and modify
  6. Testability: Creating components that are easy to test in isolation

What Problems Do Advanced Patterns Solve?

Advanced patterns address common challenges in large React applications:

  • Component Complexity: Managing complex component hierarchies
  • State Management: Coordinating state across multiple components
  • Code Reusability: Sharing logic and UI patterns across the application
  • Performance Issues: Optimizing rendering and memory usage
  • Testing Challenges: Making components testable and predictable
  • Team Collaboration: Creating consistent patterns for team development

How to Implement Compound Components? Flexible UI Composition

What are Compound Components?

Compound components are a pattern where multiple components work together to provide a flexible and intuitive API. They allow you to create complex UI components that are both powerful and easy to use.

Compound components are what enable you to build flexible, composable UI elements that give users full control over the component's structure while maintaining internal logic.

How to Create Compound Components?

import React, { createContext, useContext, useState } from 'react';

// Context for compound component
const TabsContext = createContext();

// Custom hook to use tabs context
function useTabsContext() {
const context = useContext(TabsContext);
if (!context) {
throw new Error('Tabs components must be used within Tabs');
}
return context;
}

// Main Tabs component
function Tabs({ children, defaultTab, onTabChange }) {
const [activeTab, setActiveTab] = useState(defaultTab || 0);

const handleTabChange = (index) => {
setActiveTab(index);
onTabChange?.(index);
};

const value = {
activeTab,
setActiveTab: handleTabChange
};

return (
<TabsContext.Provider value={value}>
<div className="tabs">
{children}
</div>
</TabsContext.Provider>
);
}

// TabList component
function TabList({ children, className = '' }) {
return (
<div className={`tab-list ${className}`} role="tablist">
{children}
</div>
);
}

// Tab component
function Tab({ children, index, disabled = false, className = '' }) {
const { activeTab, setActiveTab } = useTabsContext();

const handleClick = () => {
if (!disabled) {
setActiveTab(index);
}
};

const isActive = activeTab === index;

return (
<button
className={`tab ${isActive ? 'active' : ''} ${disabled ? 'disabled' : ''} ${className}`}
onClick={handleClick}
role="tab"
aria-selected={isActive}
aria-disabled={disabled}
tabIndex={isActive ? 0 : -1}
>
{children}
</button>
);
}

// TabPanels component
function TabPanels({ children, className = '' }) {
return (
<div className={`tab-panels ${className}`}>
{children}
</div>
);
}

// TabPanel component
function TabPanel({ children, index, className = '' }) {
const { activeTab } = useTabsContext();

if (activeTab !== index) {
return null;
}

return (
<div
className={`tab-panel ${className}`}
role="tabpanel"
aria-labelledby={`tab-${index}`}
>
{children}
</div>
);
}

// Usage example
function App() {
const handleTabChange = (index) => {
console.log(`Tab changed to: ${index}`);
};

return (
<div style={{ padding: '2rem' }}>
<h1>Compound Components Example</h1>

<Tabs defaultTab={0} onTabChange={handleTabChange}>
<TabList>
<Tab index={0}>Home</Tab>
<Tab index={1}>About</Tab>
<Tab index={2} disabled>Contact</Tab>
<Tab index={3}>Settings</Tab>
</TabList>

<TabPanels>
<TabPanel index={0}>
<h2>Home Content</h2>
<p>This is the home tab content. It can contain any React elements.</p>
</TabPanel>

<TabPanel index={1}>
<h2>About Content</h2>
<p>This is the about tab content with more detailed information.</p>
</TabPanel>

<TabPanel index={2}>
<h2>Contact Content</h2>
<p>This tab is disabled, so this content won't be visible.</p>
</TabPanel>

<TabPanel index={3}>
<h2>Settings Content</h2>
<p>This is the settings tab with configuration options.</p>
</TabPanel>
</TabPanels>
</Tabs>
</div>
);
}

// Attach sub-components to main component
Tabs.TabList = TabList;
Tabs.Tab = Tab;
Tabs.TabPanels = TabPanels;
Tabs.TabPanel = TabPanel;

export { Tabs, TabList, Tab, TabPanels, TabPanel };

How to Create Flexible Form Components?

import React, { createContext, useContext, useState, useCallback } from 'react';

// Form context
const FormContext = createContext();

function useFormContext() {
const context = useContext(FormContext);
if (!context) {
throw new Error('Form components must be used within Form');
}
return context;
}

// Main Form component
function Form({ children, onSubmit, initialValues = {}, validation = {} }) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});

const setValue = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));

// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }));
}
}, [errors]);

const setFieldTouched = useCallback((name) => {
setTouched(prev => ({ ...prev, [name]: true }));
}, []);

const validateField = useCallback((name, value) => {
const validator = validation[name];
if (validator) {
const error = validator(value);
setErrors(prev => ({ ...prev, [name]: error }));
return error;
}
return null;
}, [validation]);

const validateForm = useCallback(() => {
const newErrors = {};
let isValid = true;

Object.keys(validation).forEach(name => {
const error = validateField(name, values[name]);
if (error) {
newErrors[name] = error;
isValid = false;
}
});

setErrors(newErrors);
return isValid;
}, [values, validation, validateField]);

const handleSubmit = useCallback((e) => {
e.preventDefault();

if (validateForm()) {
onSubmit(values);
}
}, [values, validateForm, onSubmit]);

const value = {
values,
errors,
touched,
setValue,
setFieldTouched,
validateField,
validateForm
};

return (
<FormContext.Provider value={value}>
<form onSubmit={handleSubmit}>
{children}
</form>
</FormContext.Provider>
);
}

// FormField component
function FormField({ name, children, label, required = false }) {
const { values, errors, touched, setValue, setFieldTouched } = useFormContext();

const handleChange = (value) => {
setValue(name, value);
};

const handleBlur = () => {
setFieldTouched(name);
};

const hasError = touched[name] && errors[name];

return (
<div className="form-field">
{label && (
<label className="form-label">
{label}
{required && <span className="required">*</span>}
</label>
)}

{children({
value: values[name] || '',
onChange: handleChange,
onBlur: handleBlur,
error: hasError ? errors[name] : '',
name
})}

{hasError && (
<div className="form-error">
{errors[name]}
</div>
)}
</div>
);
}

// Input component
function Input({ type = 'text', placeholder, ...props }) {
return (
<FormField {...props}>
{({ value, onChange, onBlur, error, name }) => (
<input
type={type}
name={name}
value={value}
onChange={(e) => onChange(e.target.value)}
onBlur={onBlur}
placeholder={placeholder}
className={`form-input ${error ? 'error' : ''}`}
/>
)}
</FormField>
);
}

// TextArea component
function TextArea({ placeholder, rows = 4, ...props }) {
return (
<FormField {...props}>
{({ value, onChange, onBlur, error, name }) => (
<textarea
name={name}
value={value}
onChange={(e) => onChange(e.target.value)}
onBlur={onBlur}
placeholder={placeholder}
rows={rows}
className={`form-textarea ${error ? 'error' : ''}`}
/>
)}
</FormField>
);
}

// Select component
function Select({ options, placeholder, ...props }) {
return (
<FormField {...props}>
{({ value, onChange, onBlur, error, name }) => (
<select
name={name}
value={value}
onChange={(e) => onChange(e.target.value)}
onBlur={onBlur}
className={`form-select ${error ? 'error' : ''}`}
>
{placeholder && (
<option value="" disabled>
{placeholder}
</option>
)}
{options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
)}
</FormField>
);
}

// FormButton component
function FormButton({ children, type = 'submit', ...props }) {
return (
<button type={type} className="form-button" {...props}>
{children}
</button>
);
}

// Usage example
function ContactForm() {
const validation = {
name: (value) => !value ? 'Name is required' : '',
email: (value) => {
if (!value) return 'Email is required';
if (!/\S+@\S+\.\S+/.test(value)) return 'Email is invalid';
return '';
},
message: (value) => !value ? 'Message is required' : ''
};

const handleSubmit = (values) => {
console.log('Form submitted:', values);
alert('Form submitted successfully!');
};

return (
<div style={{ maxWidth: '500px', margin: '0 auto', padding: '2rem' }}>
<h2>Contact Form</h2>

<Form
onSubmit={handleSubmit}
validation={validation}
initialValues={{ name: '', email: '', subject: '', message: '' }}
>
<Input
name="name"
label="Name"
placeholder="Your name"
required
/>

<Input
name="email"
type="email"
label="Email"
placeholder="[email protected]"
required
/>

<Select
name="subject"
label="Subject"
placeholder="Select a subject"
options={[
{ value: 'general', label: 'General Inquiry' },
{ value: 'support', label: 'Technical Support' },
{ value: 'billing', label: 'Billing Question' },
{ value: 'feedback', label: 'Feedback' }
]}
/>

<TextArea
name="message"
label="Message"
placeholder="Your message here..."
required
/>

<FormButton>Send Message</FormButton>
</Form>
</div>
);
}

// Attach sub-components
Form.Field = FormField;
Form.Input = Input;
Form.TextArea = TextArea;
Form.Select = Select;
Form.Button = FormButton;

export { Form, FormField, Input, TextArea, Select, FormButton };

How to Implement Render Props? Flexible Component Composition

What are Render Props?

Render props are a pattern where a component accepts a function as a prop that returns React elements. This pattern allows you to share logic between components while giving you complete control over the rendering.

Render props are what enable you to create highly flexible, reusable components that can adapt to different use cases without losing their core functionality.

How to Implement Render Props?

import React, { useState, useEffect } from 'react';

// Data fetcher with render props
function DataFetcher({ url, render, renderLoading, renderError }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

fetchData();
}, [url]);

if (loading) {
return renderLoading ? renderLoading() : <div>Loading...</div>;
}

if (error) {
return renderError ? renderError(error) : <div>Error: {error}</div>;
}

return render(data);
}

// Mouse tracker with render props
function MouseTracker({ render }) {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

useEffect(() => {
const handleMouseMove = (event) => {
setMousePosition({
x: event.clientX,
y: event.clientY
});
};

window.addEventListener('mousemove', handleMouseMove);

return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);

return render(mousePosition);
}

// Local storage with render props
function LocalStorage({ key, defaultValue, render }) {
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error('Error reading localStorage:', error);
return defaultValue;
}
});

const setStoredValue = (newValue) => {
try {
setValue(newValue);
window.localStorage.setItem(key, JSON.stringify(newValue));
} catch (error) {
console.error('Error writing to localStorage:', error);
}
};

return render(value, setStoredValue);
}

// Usage examples
function App() {
return (
<div style={{ padding: '2rem' }}>
<h1>Render Props Examples</h1>

{/* Data fetching example */}
<section style={{ marginBottom: '2rem' }}>
<h2>Data Fetching</h2>
<DataFetcher
url="https://jsonplaceholder.typicode.com/users"
renderLoading={() => <div>Loading users...</div>}
renderError={(error) => <div style={{ color: 'red' }}>Error: {error}</div>}
render={(users) => (
<div>
<h3>Users ({users.length})</h3>
<ul>
{users.slice(0, 5).map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
)}
/>
</section>

{/* Mouse tracking example */}
<section style={{ marginBottom: '2rem' }}>
<h2>Mouse Tracking</h2>
<MouseTracker
render={({ x, y }) => (
<div style={{
padding: '1rem',
backgroundColor: '#f0f0f0',
borderRadius: '8px'
}}>
<p>Mouse position: ({x}, {y})</p>
<div style={{
width: '20px',
height: '20px',
backgroundColor: 'blue',
borderRadius: '50%',
position: 'absolute',
left: x - 10,
top: y - 10,
pointerEvents: 'none',
zIndex: 1000
}} />
</div>
)}
/>
</section>

{/* Local storage example */}
<section>
<h2>Local Storage</h2>
<LocalStorage
key="user-preferences"
defaultValue={{ theme: 'light', language: 'en' }}
render={(preferences, setPreferences) => (
<div style={{
padding: '1rem',
backgroundColor: '#f0f0f0',
borderRadius: '8px'
}}>
<h3>User Preferences</h3>
<div style={{ marginBottom: '1rem' }}>
<label>
Theme:
<select
value={preferences.theme}
onChange={(e) => setPreferences({
...preferences,
theme: e.target.value
})}
style={{ marginLeft: '0.5rem' }}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
</div>
<div>
<label>
Language:
<select
value={preferences.language}
onChange={(e) => setPreferences({
...preferences,
language: e.target.value
})}
style={{ marginLeft: '0.5rem' }}
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
</label>
</div>
<p style={{ marginTop: '1rem', fontSize: '0.9rem', color: '#666' }}>
Preferences are automatically saved to localStorage
</p>
</div>
)}
/>
</section>
</div>
);
}

export { DataFetcher, MouseTracker, LocalStorage };

How to Implement Higher-Order Components? Component Enhancement

What are Higher-Order Components?

Higher-Order Components (HOCs) are functions that take a component and return a new component with additional functionality. They're a powerful pattern for reusing component logic across your application.

HOCs are what enable you to enhance components with additional functionality without modifying their original implementation, promoting code reuse and separation of concerns.

How to Create Higher-Order Components?

import React, { useState, useEffect } from 'react';

// HOC for loading states
function withLoading(WrappedComponent) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '200px'
}}>
<div>Loading...</div>
</div>
);
}

return <WrappedComponent {...props} />;
};
}

// HOC for error handling
function withErrorHandling(WrappedComponent) {
return function WithErrorHandlingComponent({ error, onRetry, ...props }) {
if (error) {
return (
<div style={{
padding: '1rem',
backgroundColor: '#fee',
border: '1px solid #fcc',
borderRadius: '4px',
textAlign: 'center'
}}>
<p style={{ color: '#c33', margin: '0 0 1rem 0' }}>
Error: {error}
</p>
{onRetry && (
<button
onClick={onRetry}
style={{
backgroundColor: '#c33',
color: 'white',
border: 'none',
padding: '0.5rem 1rem',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Retry
</button>
)}
</div>
);
}

return <WrappedComponent {...props} />;
};
}

// HOC for authentication
function withAuth(WrappedComponent) {
return function WithAuthComponent({ user, ...props }) {
if (!user) {
return (
<div style={{
padding: '2rem',
textAlign: 'center',
backgroundColor: '#f8f9fa',
borderRadius: '8px'
}}>
<h3>Authentication Required</h3>
<p>Please log in to access this content.</p>
</div>
);
}

return <WrappedComponent user={user} {...props} />;
};
}

// HOC for data fetching
function withDataFetching(url, dataKey = 'data') {
return function(WrappedComponent) {
return function WithDataFetchingComponent(props) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

fetchData();
}, []);

const enhancedProps = {
...props,
[dataKey]: data,
loading,
error,
refetch: () => {
setLoading(true);
setError(null);
// Re-fetch logic would go here
}
};

return <WrappedComponent {...enhancedProps} />;
};
};
}

// HOC for performance monitoring
function withPerformanceMonitoring(WrappedComponent, componentName) {
return function WithPerformanceMonitoringComponent(props) {
useEffect(() => {
const startTime = performance.now();

return () => {
const endTime = performance.now();
const renderTime = endTime - startTime;

if (renderTime > 16) { // More than one frame
console.warn(`${componentName} took ${renderTime.toFixed(2)}ms to render`);
}
};
});

return <WrappedComponent {...props} />;
};
}

// Example components
function UserList({ users, loading, error }) {
if (loading) return <div>Loading users...</div>;
if (error) return <div>Error: {error}</div>;

return (
<div>
<h3>Users ({users?.length || 0})</h3>
<ul>
{users?.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}

function UserProfile({ user }) {
return (
<div style={{
padding: '1rem',
backgroundColor: '#f8f9fa',
borderRadius: '8px'
}}>
<h3>User Profile</h3>
<p><strong>Name:</strong> {user.name}</p>
<p><strong>Email:</strong> {user.email}</p>
<p><strong>Role:</strong> {user.role}</p>
</div>
);
}

// Enhanced components using HOCs
const UserListWithLoading = withLoading(UserList);
const UserListWithErrorHandling = withErrorHandling(UserListWithLoading);
const UserListWithData = withDataFetching(
'https://jsonplaceholder.typicode.com/users',
'users'
)(UserListWithErrorHandling);

const UserProfileWithAuth = withAuth(UserProfile);
const UserProfileWithMonitoring = withPerformanceMonitoring(
UserProfileWithAuth,
'UserProfile'
);

// Usage example
function App() {
const [user, setUser] = useState(null);

const handleLogin = () => {
setUser({ id: 1, name: 'John Doe', email: '[email protected]', role: 'admin' });
};

const handleLogout = () => {
setUser(null);
};

return (
<div style={{ padding: '2rem' }}>
<h1>Higher-Order Components Examples</h1>

<div style={{ marginBottom: '2rem' }}>
<button
onClick={user ? handleLogout : handleLogin}
style={{
backgroundColor: user ? '#dc3545' : '#007bff',
color: 'white',
border: 'none',
padding: '0.5rem 1rem',
borderRadius: '4px',
cursor: 'pointer'
}}
>
{user ? 'Logout' : 'Login'}
</button>
</div>

<section style={{ marginBottom: '2rem' }}>
<h2>User List with HOCs</h2>
<UserListWithData />
</section>

<section>
<h2>User Profile with Auth</h2>
<UserProfileWithMonitoring user={user} />
</section>
</div>
);
}

export {
withLoading,
withErrorHandling,
withAuth,
withDataFetching,
withPerformanceMonitoring
};

How to Implement Custom Hook Patterns? Advanced State Management

What are Custom Hook Patterns?

Custom hook patterns are sophisticated approaches to creating reusable stateful logic using React hooks. They enable you to encapsulate complex behavior and share it across multiple components.

Custom hook patterns are what enable you to create powerful, reusable logic that can be composed and tested independently, making your components cleaner and more maintainable.

How to Create Advanced Custom Hooks?

import React, { useState, useEffect, useCallback, useRef } from 'react';

// Custom hook for API data fetching with caching
function useApiData(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const cacheRef = useRef(new Map());

const fetchData = useCallback(async () => {
// Check cache first
if (cacheRef.current.has(url) && !options.forceRefresh) {
setData(cacheRef.current.get(url));
return;
}

try {
setLoading(true);
setError(null);

const response = await fetch(url, options.fetchOptions);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const result = await response.json();

// Cache the result
cacheRef.current.set(url, result);
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [url, options.fetchOptions, options.forceRefresh]);

useEffect(() => {
if (url) {
fetchData();
}
}, [fetchData]);

const refetch = useCallback(() => {
fetchData();
}, [fetchData]);

const clearCache = useCallback(() => {
cacheRef.current.clear();
}, []);

return {
data,
loading,
error,
refetch,
clearCache
};
}

// Custom hook for form management
function useForm(initialValues = {}, validation = {}) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);

const setValue = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));

// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }));
}
}, [errors]);

const setFieldTouched = useCallback((name) => {
setTouched(prev => ({ ...prev, [name]: true }));
}, []);

const validateField = useCallback((name, value) => {
const validator = validation[name];
if (validator) {
const error = validator(value);
setErrors(prev => ({ ...prev, [name]: error }));
return error;
}
return null;
}, [validation]);

const validateForm = useCallback(() => {
const newErrors = {};
let isValid = true;

Object.keys(validation).forEach(name => {
const error = validateField(name, values[name]);
if (error) {
newErrors[name] = error;
isValid = false;
}
});

setErrors(newErrors);
return isValid;
}, [values, validation, validateField]);

const handleSubmit = useCallback(async (onSubmit) => {
if (!validateForm()) {
return;
}

setIsSubmitting(true);
try {
await onSubmit(values);
} catch (error) {
console.error('Form submission error:', error);
} finally {
setIsSubmitting(false);
}
}, [values, validateForm]);

const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
setIsSubmitting(false);
}, [initialValues]);

return {
values,
errors,
touched,
isSubmitting,
setValue,
setFieldTouched,
validateField,
validateForm,
handleSubmit,
reset
};
}

// Custom hook for local storage with synchronization
function useLocalStorage(key, defaultValue, options = {}) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return defaultValue;
}
});

const setValue = useCallback((value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));

// Trigger custom event for cross-tab synchronization
if (options.syncAcrossTabs) {
window.dispatchEvent(new CustomEvent('localStorageChange', {
detail: { key, value: valueToStore }
}));
}
} catch (error) {
console.error(`Error writing to localStorage key "${key}":`, error);
}
}, [key, storedValue, options.syncAcrossTabs]);

useEffect(() => {
if (options.syncAcrossTabs) {
const handleStorageChange = (e) => {
if (e.detail.key === key) {
setStoredValue(e.detail.value);
}
};

window.addEventListener('localStorageChange', handleStorageChange);
return () => window.removeEventListener('localStorageChange', handleStorageChange);
}
}, [key, options.syncAcrossTabs]);

return [storedValue, setValue];
}

// Custom hook for debounced values
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);

return () => {
clearTimeout(handler);
};
}, [value, delay]);

return debouncedValue;
}

// Custom hook for previous value
function usePrevious(value) {
const ref = useRef();

useEffect(() => {
ref.current = value;
});

return ref.current;
}

// Custom hook for window size
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});

useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}

window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);

return windowSize;
}

// Usage examples
function ApiDataExample() {
const { data: users, loading, error, refetch } = useApiData(
'https://jsonplaceholder.typicode.com/users'
);

return (
<div>
<h3>API Data Example</h3>
<button onClick={refetch} disabled={loading}>
{loading ? 'Loading...' : 'Refresh'}
</button>

{error && <div style={{ color: 'red' }}>Error: {error}</div>}

{users && (
<ul>
{users.slice(0, 3).map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}

function FormExample() {
const validation = {
name: (value) => !value ? 'Name is required' : '',
email: (value) => {
if (!value) return 'Email is required';
if (!/\S+@\S+\.\S+/.test(value)) return 'Email is invalid';
return '';
}
};

const form = useForm({ name: '', email: '' }, validation);

const handleSubmit = async (values) => {
console.log('Form submitted:', values);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
alert('Form submitted successfully!');
};

return (
<div>
<h3>Form Example</h3>
<form onSubmit={(e) => {
e.preventDefault();
form.handleSubmit(handleSubmit);
}}>
<div>
<input
type="text"
value={form.values.name}
onChange={(e) => form.setValue('name', e.target.value)}
onBlur={() => form.setFieldTouched('name')}
placeholder="Name"
/>
{form.touched.name && form.errors.name && (
<div style={{ color: 'red' }}>{form.errors.name}</div>
)}
</div>

<div>
<input
type="email"
value={form.values.email}
onChange={(e) => form.setValue('email', e.target.value)}
onBlur={() => form.setFieldTouched('email')}
placeholder="Email"
/>
{form.touched.email && form.errors.email && (
<div style={{ color: 'red' }}>{form.errors.email}</div>
)}
</div>

<button type="submit" disabled={form.isSubmitting}>
{form.isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
</div>
);
}

function LocalStorageExample() {
const [preferences, setPreferences] = useLocalStorage(
'user-preferences',
{ theme: 'light', language: 'en' },
{ syncAcrossTabs: true }
);

return (
<div>
<h3>Local Storage Example</h3>
<div>
<label>
Theme:
<select
value={preferences.theme}
onChange={(e) => setPreferences({
...preferences,
theme: e.target.value
})}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
</div>
<div>
<label>
Language:
<select
value={preferences.language}
onChange={(e) => setPreferences({
...preferences,
language: e.target.value
})}
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
</label>
</div>
</div>
);
}

function App() {
return (
<div style={{ padding: '2rem' }}>
<h1>Custom Hook Patterns Examples</h1>

<section style={{ marginBottom: '2rem' }}>
<ApiDataExample />
</section>

<section style={{ marginBottom: '2rem' }}>
<FormExample />
</section>

<section>
<LocalStorageExample />
</section>
</div>
);
}

export {
useApiData,
useForm,
useLocalStorage,
useDebounce,
usePrevious,
useWindowSize
};

Why Use Advanced Patterns? The Benefits Explained

Why are Advanced Patterns Essential?

Advanced patterns provide several critical benefits for large-scale React applications:

  1. Code Reusability: Share logic and UI patterns across multiple components
  2. Maintainability: Create consistent, predictable code structures
  3. Testability: Isolate logic for easier unit testing
  4. Performance: Optimize rendering and state management
  5. Team Collaboration: Establish consistent patterns for team development
  6. Scalability: Build applications that can grow with your business

Why Choose Compound Components?

Compound components provide:

  1. Flexibility: Users have full control over component structure
  2. Intuitive API: Natural, declarative component composition
  3. Reusability: Components can be used in different combinations
  4. Maintainability: Clear separation of concerns
  5. Extensibility: Easy to add new sub-components

Why Use Render Props?

Render props offer:

  1. Maximum Flexibility: Complete control over rendering
  2. Logic Reuse: Share complex logic without prop drilling
  3. Composition: Combine multiple render prop components
  4. Testing: Easy to test logic separately from rendering
  5. Performance: Fine-grained control over re-rendering

Why Implement HOCs?

HOCs provide:

  1. Logic Enhancement: Add functionality without modifying original components
  2. Cross-Cutting Concerns: Handle authentication, loading, error states
  3. Composition: Combine multiple HOCs for complex behavior
  4. Reusability: Share enhancement logic across components
  5. Separation of Concerns: Keep business logic separate from UI

Advanced Patterns Best Practices

What are the Key Best Practices?

  1. Choose the Right Pattern: Use the pattern that best fits your use case
  2. Keep Components Simple: Don't over-engineer simple components
  3. Document Patterns: Clearly document how to use your patterns
  4. Test Thoroughly: Write comprehensive tests for your patterns
  5. Consider Performance: Be mindful of performance implications
  6. Maintain Consistency: Use consistent patterns across your application

How to Avoid Common Pitfalls?

// ❌ Don't overuse patterns for simple cases
function BadExample() {
// Using HOC for simple loading state
const SimpleComponent = withLoading(() => <div>Hello</div>);
return <SimpleComponent isLoading={false} />;
}

// ✅ Good - use patterns when they add value
function GoodExample() {
// Using HOC for complex data fetching with caching
const UserList = withDataFetching('/api/users')(UserListComponent);
return <UserList />;
}

// ❌ Don't create overly complex compound components
function BadCompoundComponent() {
return (
<ComplexForm>
<ComplexForm.Header />
<ComplexForm.Body />
<ComplexForm.Footer />
<ComplexForm.Validation />
<ComplexForm.Submission />
<ComplexForm.ErrorHandling />
</ComplexForm>
);
}

// ✅ Good - keep compound components focused
function GoodCompoundComponent() {
return (
<Form>
<Form.Field name="email" />
<Form.Field name="password" />
<Form.Submit />
</Form>
);
}

Summary: Mastering Advanced React Patterns

What Have We Learned?

In this chapter, we've explored sophisticated React patterns:

  1. Compound Components: Building flexible, composable UI components
  2. Render Props: Sharing logic with maximum flexibility
  3. Higher-Order Components: Enhancing components with additional functionality
  4. Custom Hook Patterns: Creating reusable stateful logic
  5. Best Practices: When and how to use each pattern effectively

How to Choose the Right Pattern?

  1. Simple Components: Use basic React patterns
  2. Reusable Logic: Use custom hooks
  3. Flexible UI: Use compound components
  4. Cross-Cutting Concerns: Use HOCs
  5. Maximum Flexibility: Use render props
  6. Complex State: Use custom hook patterns

Why This Matters for Your React Applications?

Understanding advanced patterns is crucial because:

  • Scalability: Patterns enable applications to grow and evolve
  • Maintainability: Consistent patterns make code easier to maintain
  • Team Productivity: Shared patterns improve team collaboration
  • Code Quality: Well-designed patterns lead to better code quality
  • Professional Development: Advanced patterns are essential for senior developers

Next Steps

Now that you understand advanced patterns, you're ready to explore:

  • Testing: How to test complex patterns and components
  • Performance: How to optimize pattern implementations
  • Architecture: How to structure large applications with patterns
  • Integration: How to combine patterns for sophisticated solutions

Remember: The best pattern is the one that solves your specific problem while keeping your code simple and maintainable.