Advanced Component Patterns
Advanced Component Patterns
Section titled “Advanced Component Patterns”Introduction
Section titled “Introduction”As React applications grow, writing simple components leads to duplication of logic and markup. To build clean, maintainable codebases, you need to understand advanced component patterns. These patterns allow you to separate logic from layout, share concerns across components, and build highly flexible, composable interfaces. This module covers the Container/Presentational design pattern, Higher-Order Components (HOCs), Render Props, and Compound Components.
Why do we need this?
Section titled “Why do we need this?”If every component in your application handles data fetching, business logic, state management, and HTML layout updates, your components will grow massive, making them difficult to test, reuse, or adapt.
Problem Statement
Section titled “Problem Statement”Consider an application that displays user directories, product catalogs, and transaction history. Each list component needs to:
- Fetch data from an API endpoint.
- Show a loading spinner during the fetch.
- Show an error banner if the fetch fails.
- Render the list of items.
If you write this logic inside every component, you duplicate the loading, error, and fetch boilerplate code across all three lists. Furthermore, if you want to switch from a list view to a grid view, you have to rewrite the rendering logic inside a component that is already cluttered with data-fetching code. We need patterns that split logic from presentation, wrap cross-cutting concerns, and delegate layout control.
Real World Story
Section titled “Real World Story”In the early days of React, developers struggled to share code across components. Class components did not support custom hooks, so engineers relied on Mixins to inject functionality. However, mixins caused method name collisions, hidden dependencies, and made code difficult to understand.
To solve this, React pioneer Dan Abramov proposed the Presentational and Container Components pattern, which separated data-fetching logic from layout presentation. Shortly after, the community popularized Higher-Order Components (HOCs) and Render Props to encapsulate shared logic (like tracking mouse coordinates or checking authentication status). Today, while custom hooks have replaced HOCs and Render Props for sharing pure state logic, these patterns remain critical for building modular layouts, component libraries, and composite UI structures.
Real World Analogy
Section titled “Real World Analogy”Think of advanced component patterns like a Restaurant Operations Model compared to a Single Food Vendor.
- Without Patterns (Single Food Vendor): The vendor takes orders, cooks the food, serves the customers, cleans the tables, and runs the accounting. If the business grows, hiring more vendors is difficult because each new hire must learn how to perform every job.
- With Design Patterns (Restaurant Operations):
- Container / Presentational: The Cashier (Container Component) takes the orders and fetches the payment. The Chef (Presentational Component) prepares the food and plates it, without needing to know about cash drawer balances.
- Higher-Order Component: The health inspector attaches a standard sanitization station (HOC wrapper) to the restaurant entrance, protecting the kitchen without changing how food is cooked inside.
- Compound Components: The dining table set consists of a table, chairs, and plates. They share the same space (Context) to work together, but can be rearranged to fit any room layout.
Visual Explanation
Section titled “Visual Explanation”Below is a diagram showing the comparison of Container vs. Presentational patterns, alongside the wrapping structure of Higher-Order Components.
Presentational vs. Container Pattern
Section titled “Presentational vs. Container Pattern”[Container Component (Logic)] ──(Passes Fetched Data)──> [Presentational Component (UI)] └─ Handles fetching & loading state └─ Purely renders HTML & CSSHigher-Order Component Wrapper
Section titled “Higher-Order Component Wrapper”[HOC Wrapper (e.g., withAuth)] └──> [Original Component] ──> [Secured Component Output]flowchart TD subgraph Container/Presentational Container[Container Component\n- Fetches Data\n- Manages State] -->|Passes props| Presentational[Presentational Component\n- Receives Props\n- Renders HTML Layout] end subgraph Higher-Order Component Wrapper[HOC Function: withAuth] -->|Wraps| Component[Base Component: Dashboard] Wrapper --> Output[Exported Component: withAuthDashboard] endInternal Working
Section titled “Internal Working”The core of these patterns lies in composition. By separating logic from layout, we can build components that accept other components as inputs.
- Render Props: A component accepts a function as a prop and calls it to render its UI, passing internal state values as parameters.
- Compound Components: Sibling components share state implicitly using React Context. This allows them to work together without forcing you to pass props down manually to each element.
sequenceDiagram participant Parent as Accordion (Compound Parent) participant Context as Provider Context participant Item as AccordionItem (Compound Child)
Parent->>Context: Initialize activeIndex state Item->>Context: Subscribe to activeIndex context Note over Item: User clicks AccordionItem Item->>Context: Trigger setActiveIndex(index) Context->>Parent: Update activeIndex state Context-->>Item: Notify activeIndex changed (Item toggles visibility)Architecture
Section titled “Architecture”Compound components organize layouts cleanly. The parent component acts as a state coordinator, providing context to nested children.
flowchart TD Tabs[Tabs Parent Component] -->|Provides activeTab Context| TabList[TabList Component] Tabs -->|Provides activeTab Context| TabPanels[TabPanels Component] TabList --> Tab1[Tab Button 1] TabList --> Tab2[Tab Button 2] TabPanels --> Panel1[Panel Content 1] TabPanels --> Panel2[Panel Content 2]Step-by-Step Flow
Section titled “Step-by-Step Flow”When using the Compound Components pattern to build a dropdown menu, the following steps occur:
flowchart TD Step1[1. Menu Parent initializes state for isOpen] --> Step2[2. Menu Parent wraps children in a Context Provider] Step2 --> Step3[3. Menu.Button child subscribes to Context and toggles isOpen on click] Step3 --> Step4[4. Menu.List child reads Context and renders menu items conditionally] Step4 --> Step5[5. React updates the DOM based on Context changes without prop drilling]Syntax
Section titled “Syntax”Here is the basic syntax for a Higher-Order Component:
// A HOC is a function that returns a wrapped componentfunction withSubscription(WrappedComponent) { return function SubscriptionComponent(props) { // 1. Add custom state or effects // 2. Render wrapped component with original props + new props return <WrappedComponent {...props} extraData={data} />; };}Basic Example
Section titled “Basic Example”Here is a simple example showing the Container/Presentational design pattern. The Container component fetches data from an API, while the Presentational component renders the list layout.
import React, { useState, useEffect } from 'react';
// 1. Presentational (Dumb) Component// Receives data via props and handles layout styling onlyfunction UserListUI({ users, loading }) { if (loading) return <p>Loading directory...</p>; return ( <ul> {users.map(user => <li key={user.id}>{user.name}</li>)} </ul> );}
// 2. Container (Smart) Component// Manages data fetching, loading state, and passes data to the UI componentexport default function UserDirectoryContainer() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true);
useEffect(() => { fetch('https://jsonplaceholder.typicode.com/users') .then(res => res.json()) .then(data => { setUsers(data); setLoading(false); }); }, []);
return ( <div style={{ padding: '16px', background: '#fafafa' }}> <h3>Directory</h3> <UserListUI users={users} loading={loading} /> </div> );}Intermediate Example
Section titled “Intermediate Example”An intermediate component showing the Render Props pattern. The MouseTracker component manages mouse movement state and passes the coordinates to a render function prop, allowing other components to render their UI dynamically based on the coordinates.
import React, { useState } from 'react';
// Reusable behavior component using the Render Props patternfunction MouseTracker({ render }) { const [coords, setCoords] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => { // Find mouse coordinates relative to the tracker element const rect = e.currentTarget.getBoundingClientRect(); setCoords({ x: Math.floor(e.clientX - rect.left), y: Math.floor(e.clientY - rect.top) }); };
return ( <div onMouseMove={handleMouseMove} style={{ height: '200px', border: '2px dashed gray', position: 'relative' }} > {/* Call the render function prop, passing state as arguments */} {render(coords)} </div> );}
export default function GameScreen() { return ( <div style={{ padding: '16px' }}> <h3>Interactive Game Arena</h3> <MouseTracker render={({ x, y }) => ( <span style={{ position: 'absolute', left: x + 10, top: y + 10, pointerEvents: 'none' }}> 🎯 Pointer at ({x}px, {y}px) </span> )} /> </div> );}Advanced Example
Section titled “Advanced Example”An advanced example showing the Compound Components pattern. It uses React Context to share state implicitly between a parent <Select> component and its child <Select.Option> elements, building a clean and flexible select menu.
import React, { useState, createContext, useContext } from 'react';
// Create Context to share state between parent and child optionsconst SelectContext = createContext(null);
// 1. Parent Compound Componentexport function CustomSelect({ children, defaultValue, onChange }) { const [selectedValue, setSelectedValue] = useState(defaultValue);
const selectOption = (value) => { setSelectedValue(value); if (onChange) onChange(value); };
return ( <SelectContext.Provider value={{ selectedValue, selectOption }}> <div style={{ border: '1px solid #ccc', padding: '12px', borderRadius: '6px' }}> {children} </div> </SelectContext.Provider> );}
// 2. Child Compound Option Componentfunction Option({ value, children }) { const context = useContext(SelectContext); if (!context) { throw new Error('Select.Option must be used within a CustomSelect wrapper.'); }
const { selectedValue, selectOption } = context; const isSelected = selectedValue === value;
return ( <div onClick={() => selectOption(value)} style={{ padding: '8px', margin: '4px 0', cursor: 'pointer', backgroundColor: isSelected ? '#e3f2fd' : 'transparent', borderLeft: isSelected ? '4px solid #1e88e5' : '4px solid transparent' }} > {children} </div> );}
// Attach the Option component to the CustomSelect objectCustomSelect.Option = Option;
export default function FormSelectPage() { return ( <div style={{ maxWidth: '300px' }}> <h3>Compound Component Form</h3> <CustomSelect defaultValue="dev" onChange={val => console.log('Selected:', val)}> <CustomSelect.Option value="design">Graphic Designer</CustomSelect.Option> <CustomSelect.Option value="dev">Software Engineer</CustomSelect.Option> <CustomSelect.Option value="product">Product Manager</CustomSelect.Option> </CustomSelect> </div> );}Production Example
Section titled “Production Example”A production-grade Higher-Order Component (HOC) called withAuthentication. This wrapper checks the user’s login status, handles loading states, and redirects unauthenticated users, protecting routes and dashboard layouts from unauthorized access.
import React, { useState, useEffect } from 'react';
// Mock auth check helperconst checkSessionValidity = async () => { // Simulates network latency await new Promise(resolve => setTimeout(resolve, 800)); const token = localStorage.getItem('user_session_token'); return !!token;};
// 1. Higher-Order Component Definitionexport function withAuthentication(WrappedComponent, FallbackComponent = null) { return function SecuredComponent(props) { const [authStatus, setAuthStatus] = useState({ loading: true, authenticated: false });
useEffect(() => { let isSubscribed = true; async function runAuthCheck() { const hasSession = await checkSessionValidity(); if (isSubscribed) { setAuthStatus({ loading: false, authenticated: hasSession }); } } runAuthCheck(); return () => { isSubscribed = false; }; }, []);
if (authStatus.loading) { return <div aria-live="polite">Verifying credentials...</div>; }
if (!authStatus.authenticated) { if (FallbackComponent) { return <FallbackComponent />; } return ( <div role="alert" style={{ border: '1px dashed red', padding: '16px', color: 'red' }}> <h4>Access Denied</h4> <p>You must sign in to view this admin panel directory.</p> </div> ); }
// Pass original props to the wrapped component return <WrappedComponent {...props} />; };}
// 2. Base Admin Panel Component to protectfunction AdminConsole({ adminRole }) { return ( <div> <h2>Secure Enterprise console</h2> <p>Privileges: <strong>{adminRole}</strong></p> </div> );}
// Export the protected component wrapped in the auth HOCexport const SecureAdminConsole = withAuthentication(AdminConsole);Folder Structure
Section titled “Folder Structure”advanced-component-patterns/├── src/│ ├── components/│ │ ├── CustomSelect.jsx│ │ └── withAuthentication.jsx│ ├── App.jsx│ └── main.jsx├── package.json└── vite.config.jsBest Practices
Section titled “Best Practices”💡 Did You Know?
Higher-Order Components are not part of the core React API. They are a design pattern that leverages JavaScript’s functional nature: functions that take a component as an argument and return a new, wrapped component.
🚀 Best Practices
- Pass all unrelated props through to the wrapped component inside your HOCs using the spread operator (
{...props}). This ensures they continue to work correctly when wrapped. - Provide a helpful display name for HOCs (e.g.,
withAuth(MyComponent)) to make debugging easier in the React DevTools. - Throw descriptive errors inside Compound Components if child elements are rendered outside their parent context.
Common Mistakes
Section titled “Common Mistakes”⚠ Common Mistakes
Defining HOCs inside the Render Method
Section titled “Defining HOCs inside the Render Method”Defining a Higher-Order Component inside the render method of another component causes React to recreate the wrapped component on every render. This resets the component’s state and lifecycle methods, causing slow rendering performance and screen flashes.
// ❌ WRONGfunction Parent(props) { // Recreated on every render, resetting state and DOM nodes const ProtectedChild = withAuthentication(ChildComponent); return <ProtectedChild />;}
// RIGHT// Define HOC wrapper outside the component once, or inside useEffectconst ProtectedChild = withAuthentication(ChildComponent);function Parent(props) { return <ProtectedChild />;}Performance Notes
Section titled “Performance Notes”⚡ Performance Tips Using Compound Components with React Context can trigger re-renders in all subscribed child elements whenever the context value changes. To optimize performance, separate state providers or use memoization on the child nodes.
Performance Example
Section titled “Performance Example”Using useMemo to cache context values and prevent unnecessary re-renders in child components:
// Optimize the provider value reference inside parent componentsconst contextValue = React.useMemo(() => ({ selectedValue, selectOption}), [selectedValue]); // Only updates when selectedValue changes
return ( <SelectContext.Provider value={contextValue}> {children} </SelectContext.Provider>);Accessibility Notes
Section titled “Accessibility Notes”♿ Accessibility Tips
- Ensure that Compound Components like Tabs or Select Menus map the appropriate ARIA roles (e.g.,
role="tablist",role="tab",role="tabpanel") to their HTML markup. - Bind keyboard events to Tab selectors so users can navigate between options using the arrow keys.
SEO Notes
Section titled “SEO Notes”When dividing layouts into Smart and Dumb components, ensure that presentational components render semantic HTML structures. This allows search engines to parse and index your layouts correctly.
Interview Questions
Section titled “Interview Questions”🎯 Interview Tips
In an interview, explain HOCs as “functions that take a component as an argument and return a new, wrapped component, adding shared features or behaviors.” Contrast this with Render Props, which delegate layout control by accepting a render function prop.
Q1: What is the Compound Component Pattern, and when should you use it?
Section titled “Q1: What is the Compound Component Pattern, and when should you use it?”Answer: The Compound Component pattern is a design pattern where multiple components work together to manage shared state implicitly via React Context. It is used to build flexible and highly customizable UI controls (like accordions, drop-down menus, tabs, or select menus). This allows developers to rearrange components without having to pass props down manually to each element.
Q2: What are the main drawbacks of using Higher-Order Components (HOCs)?
Section titled “Q2: What are the main drawbacks of using Higher-Order Components (HOCs)?”Answer: The main drawbacks include:
- Prop Name Collisions: If a HOC injects a prop with the same name as an existing prop, it will overwrite the original prop value.
- Implicit Props: It is not always clear which props are injected by the HOC and which are passed from the parent component, making the code harder to read.
- Boilerplate: Writing custom TypeScript interfaces for HOC wrappers can be complex and verbose.
-
What is the primary role of a Container Component?
- A) Renders complex CSS styling grids.
- B) Manages data fetching, business logic, and local state.
- C) Defines route paths for navigation.
- D) Translates layouts into mobile-compatible builds.
- Answer: B
-
How does the Render Props pattern share logic?
- A) By inheriting from a base class component.
- B) By injecting code blocks using mixins.
- C) By accepting a function as a prop and calling it to render the UI, passing internal state as arguments.
- D) By copying files into a build directory.
- Answer: C
-
Which API is used by Compound Components to share state implicitly between siblings?
- A) React Reducer hook
- B) React Context API
- C) WebSockets
- D) Local Storage
- Answer: B
-
Why is defining a HOC inside a component’s render method discouraged?
- A) It throws compilation syntax errors.
- B) It forces React to recreate the wrapped component on every render, resetting state and DOM nodes.
- C) It exposes secret API keys to the browser.
- D) It disables CSS imports.
- Answer: B
-
Which pattern is typically used to implement route protection in React routers?
- A) Presentational Component
- B) Render Props
- C) Higher-Order Component (HOC)
- D) Recursive function component
- Answer: C
Practice Exercise
Section titled “Practice Exercise”Exercise 1: Container/Presentational Refactoring
Section titled “Exercise 1: Container/Presentational Refactoring”Refactor this monolithic component into separate Container and Presentational components:
function MonolithicUserLoader() { const [data, setData] = React.useState(null); React.useEffect(() => { fetch('/api').then(res => res.json()).then(setData); }, []); if (!data) return <p>Loading...</p>; return <div>Welcome, {data.username}</div>;}Exercise 2: withLogger HOC
Section titled “Exercise 2: withLogger HOC”Write a HOC called withLogger that prints the component’s name and props to the console every time it renders.
Exercise 3: Compound Accordion Structure
Section titled “Exercise 3: Compound Accordion Structure”Create an accordion component structure using the Compound Components pattern, consisting of: <Accordion>, <Accordion.Item>, <Accordion.Header>, and <Accordion.Panel>.
Debugging Exercise
Section titled “Debugging Exercise”The Isolated Compound Child Error
Section titled “The Isolated Compound Child Error”A developer is trying to build a select list using compound components, but they get a runtime error: SelectContext is undefined. Identify the bug and write the fix.
import React, { useContext } from 'react';import { SelectContext } from './CustomSelect.jsx';
export default function CustomOption({ value, children }) { // BUG: Not verifying if the child is rendered within the provider context const { selectedValue, selectOption } = useContext(SelectContext);
return ( <div onClick={() => selectOption(value)}> {children} </div> );}
// App.jsx// The developer renders the option outside the select parent containerexport function App() { return <CustomOption value="1">Standalone Option</CustomOption>;}Solution
Section titled “Solution”The child compound component is rendered outside the parent component’s context provider, causing the context value to be undefined. To fix this, add a check to verify the context is defined:
// Corrected CustomOption componentexport default function CustomOption({ value, children }) { const context = useContext(SelectContext);
// Guard check: ensures the component is wrapped in the parent provider if (!context) { throw new Error('CustomOption must be rendered within a CustomSelect container.'); }
const { selectedValue, selectOption } = context; return ( <div onClick={() => selectOption(value)} style={{ cursor: 'pointer' }}> {children} </div> );}Real-world Scenario
Section titled “Real-world Scenario”You are building a dynamic form generator that renders form controls based on a JSON configuration schema received from a backend API. Explain how you would render these inputs dynamically without using conditional JSX loops.
- Design Strategy: Create a map of form components. Look up the appropriate component using the input field type from the schema, and instantiate it using
React.createElement(componentMap[field.type], { ...field.props })to render the correct input field dynamically.
Interview Coding Question
Section titled “Interview Coding Question”Problem Statement
Section titled “Problem Statement”Write a Higher-Order Component called withLoadingDelay that delays rendering the wrapped component for 1500ms and displays a loading spinner in the meantime.
import React, { useState, useEffect } from 'react';
// 1. HOC Wrapperexport function withLoadingDelay(WrappedComponent) { return function DelayedComponent(props) { const [loading, setLoading] = useState(true);
useEffect(() => { const timer = setTimeout(() => { setLoading(false); }, 1500); return () => clearTimeout(timer); }, []);
if (loading) { return <div className="spinner">Spinning loader active...</div>; }
return <WrappedComponent {...props} />; };}
// 2. Base Componentfunction HelloCard({ name }) { return <h1>Hello, {name}!</h1>;}
// 3. Export wrapped componentexport const DelayedHello = withLoadingDelay(HelloCard);Mini Project
Section titled “Mini Project”Tabs Builder Studio
Section titled “Tabs Builder Studio”Build a tabbed container library using the Compound Component pattern:
- The structure must support:
<Tabs defaultValue="t1"><Tabs.List><Tabs.Trigger value="t1">Tab 1</Tabs.Trigger><Tabs.Trigger value="t2">Tab 2</Tabs.Trigger></Tabs.List><Tabs.Content value="t1">Content 1</Tabs.Content><Tabs.Content value="t2">Content 2</Tabs.Content></Tabs>
- Toggle active states implicitly using React Context.
- Highlight the active tab selection and display the matching content panel on screen.
Summary
Section titled “Summary”🧠 Memory Tricks
HOCs are filters - Think of a HOC like a camera filter. The camera (base component) takes the picture, but the filter (HOC) alters the final image (adding auth, logging, or styles) without modifying the camera.
📖 Summary
Advanced component patterns separate concerns and optimize layout architectures. The Container/Presentational pattern separates logic from styling, HOCs wrap cross-cutting concerns, Render Props share state values dynamically, and Compound Components share state implicitly between sibling elements.
Cheat Sheet
Section titled “Cheat Sheet”// Render Props pattern syntax<Tracker render={(state) => <Display {...state} />} />
// Compound Components pattern syntax<Parent> <Parent.Child value="A">Item A</Parent.Child></Parent>