Frontend Development 11 min read

React Compound Components Pattern: A Complete Guide

The article introduces the React Compound Components pattern, showing how a parent component with shared Context can expose reusable child parts—like Title, FormInputs, and SubmitButtons—so the same logic can be flexibly arranged in different layouts such as full‑page or modal, improving reuse, readability, and maintainability.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
React Compound Components Pattern: A Complete Guide

Have you ever encountered a situation where the same React component needs to display completely different layouts or styles in different scenarios? This article introduces a revolutionary component pattern that can completely solve this problem: Compound Components .

Through concrete examples, this article will help you understand this revolutionary React component pattern and teach you how to immediately apply it to your own projects.

Scenario: Similar Components with Different Contexts

Imagine you are building a contact management application. You would encounter:

Editing contact information on a standalone page.

Editing contact information in a modal.

Although these two interfaces have similar input fields, save/cancel buttons, and titles, their layouts are completely different:

Page Mode : Full-page layout with title and buttons in the top area.

Modal Mode : Compact layout that must follow the modal's style constraints.

In the past, you might have chosen to:

Create two separate components, resulting in a lot of duplicate code.

Create a complex component that conditionally renders based on props.

However, both approaches have drawbacks - the code is difficult to maintain and has poor scalability.

So, is there a better way? Let's see how to elegantly solve this using the Compound Components Pattern .

Solution: Compound Components Pattern

The Compound Components Pattern is a compositional component design approach. You create a parent component that manages state and behavior, and expose a series of child components for rendering different UI parts.

You can think of it as similar to HTML's <select> and <option> - the components work together, but the specific arrangement can be freely combined.

Usage example:

Edit Page Component Example:

// EditContactPage.jsx
function EditContactPage({ contactId }) {
return (
<PageLayout>
<EditContact.Root contactId={contactId}>
<div className="header">
<EditContact.Title />
<EditContact.SubmitButtons />
</div>
<div className="form-container">
<EditContact.FormInputs />
</div>
</EditContact.Root>
</PageLayout>
);
}

Modal Component Example:

// ContactModal.jsx
function ContactModal({ contactId, onClose }) {
return (
<Modal
onClose={onClose}
title={<EditContact.Title />}
footer={<EditContact.SubmitButtons />}
>
<EditContact.Root contactId={contactId}>
<EditContact.FormInputs />
</EditContact.Root>
</Modal>
);
}

The code above clearly shows the elegance of this pattern: the same logical component can be flexibly adapted to different scenarios by simply adjusting the layout.

How to Implement the Compound Components Pattern?

Core elements for implementing compound components:

Use Context to share state.

Parent component manages logic and exposes it to child components.

Child components consume shared state through Context.

Step 1: Create Context

import { createContext, useContext, useState, useEffect } from 'react';
const EditContactContext = createContext(null);
function useEditContactContext() {
const context = useContext(EditContactContext);
if (!context) {
throw new Error("子组件必须位于 EditContact.Root 内!");
}
return context;
}

Step 2: Create Parent Component Root to Manage State

function Root({ contactId, children }) {
const [formData, setFormData] = useState({
name: '',
email: '',
phone: ''
});
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
// 获取联系人信息
const { data: contact } = useGetContact(contactId);
// 保存联系人信息
const saveContact = useSaveContact({
onSuccess: () => { /*成功处理逻辑*/ },
onError: (err) => setError(err.message)
});
useEffect(() => {
if (contact) {
setFormData(contact);
}
}, [contact]);
const handleSubmit = async () => {
setLoading(true);
try {
await saveContact.mutateAsync({ id: contactId, ...formData });
} finally {
setLoading(false);
}
};
const contextValue = {
contact,
formData,
setFormData,
error,
loading,
handleSubmit
};
return (
<EditContactContext.Provider value={contextValue}>
{children}
</EditContactContext.Provider>
);
}

Step 3: Create Child Components to Consume Context

Title Component:

function Title() {
const { contact } = useEditContactContext();
return <>{contact ? `编辑 ${contact.name}` : "创建联系人"}</>;
}

Submit Buttons Component:

function SubmitButtons() {
const { handleSubmit, loading } = useEditContactContext();
return (
<div>
<button onClick={handleSubmit} disabled={loading}>
{loading ? '保存中...' : '保存'}
</button>
<button>取消</button>
</div>
);
}

Form Inputs Component:

function FormInputs() {
const { formData, setFormData, error } = useEditContactContext();
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
return (
<form>
{error && <div className="error">{error}</div>}
<input name="name" value={formData.name} onChange={handleChange} placeholder="姓名" />
<input name="email" value={formData.email} onChange={handleChange} placeholder="邮箱" />
<input name="phone" value={formData.phone} onChange={handleChange} placeholder="电话" />
</form>
);
}

Step 4: Export Compound Components

const EditContact = {
Root,
Title,
SubmitButtons,
FormInputs
};
export default EditContact;

Why This Pattern is Recommended?

After using it in practice, I感受到它强大的优势:

Flexible Layout : Freely combine child components to achieve different layouts.

Logic Reuse : Core logic is centrally managed, avoiding duplication.

High Readability : JSX clearly shows component structure, making it easier to understand.

Strong Maintainability : Modifying logic requires changes in only one place.

Community Recognition : Many popular component libraries (like Chakra UI, Radix UI, shadcn/ui) widely use this pattern.

When to Use This Pattern?

The following scenarios are suitable for using the compound components pattern:

The same component needs to adapt to different layouts.

Complex state needs to be shared across multiple child components.

Building component libraries or design systems that need layout composition flexibility.

Summary

Compound components make component layouts more flexible.

Use React Context to manage shared state.

Clear separation of logic and layout improves maintainability.

Especially suitable for components that need to adapt to different scenario layouts.

Next time you encounter a React component that needs flexible layout implementation, try the compound components pattern. Perhaps, like me, you will completely change your React development approach.

JavaScriptfrontend developmentstate-managementReactUI ComponentComponent Design PatternCompound ComponentsReact Context
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.