React: Component Composition and Reuse

Last updated: 2026-08-26

Combining components is like building with Legos—not every piece needs to be unique. Good component design means that the basic building blocks (subcomponents) are flexible and can be assembled in various ways to create any shape.


1. What You'll Learn



2. The Story of a Design System

(1) Pain Point: Reinventing the wheel on every page

Charlie is the company's front-end architect. He noticed that every page was reinventing the wheel:

JSX
// Page A:Custom Pop-up Window
<div style={{
  position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
  backgroundColor: 'rgba(0,0,0,0.5)',
  display: 'flex', alignItems: 'center', justifyContent: 'center'
}}>
  <div style={{ backgroundColor: 'white', padding: '24px', borderRadius: '8px' }}>
    <h2>Confirm Deletion</h2>
    <p>Are you sure you want to delete this record??</p>
    <button>Cancel</button>
    <button>Confirm</button>
  </div>
</div>

// Page B:Another pop-up,But the style is slightly different
<div style={{
  position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
  backgroundColor: 'rgba(0,0,0,0.3)',
  display: 'flex', alignItems: 'center', justifyContent: 'center'
}}>
  <div style={{ backgroundColor: 'white', padding: '32px', borderRadius: '12px' }}>
    <h2>Edit User</h2>
    <input placeholder="Username" />
    <input placeholder="Email" />
    <button>Save</button>
    <button>Cancel</button>
  </div>
</div>
▶ Try it Yourself

Issue: The styles for the mask layer and the container are duplicated across two pages. If you want to change the size of the pop-up’s rounded corners, you have to edit 20 pages.

(2) Solution to the Combinatorial Model

JSX
// General Popup Component(Responsible only for structure and style,Not interested in the content)
function Modal({ isOpen, onClose, title, children }) {
  if (!isOpen) return null

  return (
    <div style={overlayStyle} onClick={onClose}>
      <div style={modalStyle} onClick={e => e.stopPropagation()}>
        {title && <h2 style={{ margin: '0 0 16px 0' }}>{title}</h2>}
        {children}  {/* The content is determined by the user. */}
      </div>
    </div>
  )
}

// Page A:Confirm Deletion
<Modal isOpen={showDelete} onClose={() => setShowDelete(false)} title="Confirm Deletion">
  <p>Are you sure you want to delete this record??This action cannot be undone.。</p>
  <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
    <button onClick={() => setShowDelete(false)}>Cancel</button>
    <button style={{ backgroundColor: '#ff4d4f', color: 'white' }}>Confirm Deletion</button>
  </div>
</Modal>

// Page B:Edit User
<Modal isOpen={showEdit} onClose={() => setShowEdit(false)} title="Edit User">
  <input placeholder="Username" />
  <input placeholder="Email" />
  <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '16px' }}>
    <button>Save</button>
    <button onClick={() => setShowEdit(false)}>Cancel</button>
  </div>
</Modal>
▶ Try it Yourself

Benefits: Centralized management of pop-up styles (change one place, change all); content is entirely determined by the user; a single Modal component is reused across 20 pages.

100%
graph TB
    subgraph "Combination Mode"
        A[Modal Components] --> B[Overlay]
        A --> C[Container modal]
        C --> D[Title title]
        C --> E[**children slot**]
        E --> F[The content is determined by the parent component]
    end
    
    style E fill:#1890ff,color:#fff


3. children slot

Slot Mode Syntax Use Cases
Single Slot {children} Simple Container (Card, Layout)
Multi-slot {props.header} {props.footer} Multi-region layout (Page, Dialog)
renderProps {props.renderItem(item)} Custom Rendering of Lists/Tables
Function Subcomponent {props.children(data)} Data Propagation + Custom Rendering

(1) Basic Usage

children is a special Prop that represents the content between the component's tags.

JSX
// Component Definition
function Card({ title, children }) {
  return (
    <div style={{
      border: '1px solid #f0f0f0',
      borderRadius: '8px',
      padding: '16px',
      marginBottom: '16px'
    }}>
      {title && <h3 style={{ margin: '0 0 12px 0', color: '#333' }}>{title}</h3>}
      <div>{children}</div>
    </div>
  )
}

// How to Use 1:A single element
<Card title="Announcement">
  <p>The system will go live tonight 22:00 Perform maintenance</p>
</Card>

// How to Use 2:Multiple elements
<Card title="User Information">
  <p>Name:Alice</p>
  <p>Characters:Administrator</p>
  <p>Email:alice@example.com</p>
</Card>

// How to Use 3:and even other components
<Card title="Statistical Overview">
  <StatCard label="Number of users" value={1234} />
  <StatCard label="Number of Orders" value={567} />
</Card>
▶ Try it Yourself

(2) Multi-slot mode

If a component requires multiple slot positions, you can use named props (such as header, footer, main):

JSX
function PageLayout({ header, sidebar, children, footer }) {
  return (
    <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
      {/* Header Socket */}
      <header style={{ backgroundColor: '#001529', color: 'white', padding: '16px' }}>
        {header}
      </header>

      <div style={{ display: 'flex', flex: 1 }}>
        {/* Sidebar Slot */}
        <aside style={{ width: '240px', backgroundColor: '#f5f5f5', padding: '16px' }}>
          {sidebar}
        </aside>
        
        {/* Main Content Slot(children) */}
        <main style={{ flex: 1, padding: '24px' }}>
          {children}
        </main>
      </div>

      {/* Bottom Slot */}
      <footer style={{ backgroundColor: '#f0f0f0', padding: '12px', textAlign: 'center' }}>
        {footer}
      </footer>
    </div>
  )
}

// Usage
<PageLayout
  header={<><h1>Management System</h1><nav>Navigation Menu</nav></>}
  sidebar={<><h3>List of Features</h3><ul><li>User Management</li><li>Order Management</li></ul></>}
  footer={<p>© 2026 web-tutorial.com</p>}
>
  <h2>Welcome back,Administrator</h2>
  <p>Today's Orders:128 orders</p>
  <p>New Users:23 users</p>
</PageLayout>
▶ Try it Yourself

▶ Example: Generic List Component

Output:

TEXT 📖 Display only
Button: Add Item. Displays: ") : (        data.map((item, index) => ("
JSX
// ============================================
// Example:Generic List Component(Multi-slot + children)
// ============================================

function DataList({ data, renderItem, header, emptyMessage = 'No data available' }) {
  return (
    <div style={{ border: '1px solid #f0f0f0', borderRadius: '8px' }}>
      {/* Header Socket */}
      {header && <div style={{ padding: '12px 16px', borderBottom: '1px solid #f0f0f0', backgroundColor: '#fafafa' }}>{header}</div>}
      
      {/* List Contents */}
      {data.length === 0 ? (
        <div style={{ padding: '40px', textAlign: 'center', color: '#999' }}>{emptyMessage}</div>
      ) : (
        data.map((item, index) => (
          <div key={item.id} style={{
            padding: '12px 16px',
            borderBottom: index < data.length - 1 ? '1px solid #f0f0f0' : 'none',
            transition: 'background-color 0.2s'
          }}
          onMouseEnter={e => e.currentTarget.style.backgroundColor = '#fafafa'}
          onMouseLeave={e => e.currentTarget.style.backgroundColor = 'transparent'}
          >
            {renderItem(item)}
          </div>
        ))
      )}
      
      {/* children You can add additional action buttons */}
      {data.length > 0 && <div style={{ padding: '12px 16px', borderTop: '1px solid #f0f0f0' }}>{data.length} records</div>}
    </div>
  )
}

// Examples of Use 1:User List
<DataList
  data={users}
  header={<h3 style={{ margin: 0 }}>User List</h3>}
  renderItem={user => (
    <div style={{ display: 'flex', justifyContent: 'space-between' }}>
      <span>{user.name}</span>
      <span style={{ color: '#999' }}>{user.role}</span>
    </div>
  )}
/>

// Examples of Use 2:Product List
<DataList
  data={products}
  header={<h3 style={{ margin: 0 }}>Product List <button>Add Item</button></h3>}
  renderItem={product => (
    <div style={{ display: 'flex', justifyContent: 'space-between' }}>
      <span>{product.name}</span>
      <span style={{ color: '#ff4d4f' }}>${product.price}</span>
    </div>
  )}
  emptyMessage="No products have been listed yet."
/>

Output:

TEXT 📖 Display only
Reusable list: header slot, custom renderItem, empty state, record count footer. Hover highlight on items.


4. Principles of Component Decomposition

Good components are like good functions—they have a single responsibility, are reusable, and are easy to test.

Principle Explanation Counterexample
Single Responsibility A component does only one thing A component both displays user information and processes data exports
Minimize Interfaces The fewer props, the better (≤5) A component has 15 props
Data Flows Downward Data flows unidirectionally from parent to child Child components directly modify global state
No side effects Identical props render identical results Modify the DOM or call APIs directly within the component

▶ Example: Before splitting vs. After splitting

Output:

TEXT 📖 Display only
Displays: ") : (        data.map((item, index) => (". Button: Add Item
JSX
// ============================================
// Example:Component Breakdown——from "Giant Components"to "Widget"
// ============================================

// ---- Before the split:One component does it all ----
function GiantUserPage({ userId }) {
  const [user, setUser] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [editing, setEditing] = React.useState(false)
  const [name, setName] = React.useState('')
  const [email, setEmail] = React.useState('')
  const [posts, setPosts] = React.useState([])
  // ... 100 Multiple lines of logic mixed together

  return (
    <div>
      {/* User Information、Edit Form、The list of articles is all crammed together */}
    </div>
  )
}

// ---- After the split:Each to their own role ----
function UserPage({ userId }) {
  return (
    <div>
      <UserProfile userId={userId} />  {/* Responsible only for displaying user information */}
      <UserEditForm userId={userId} />  {/* Responsible only for editing user content */}
      <UserPosts userId={userId} />     {/* Responsible only for displaying the list of articles */}
    </div>
  )
}

// Each subcomponent can be developed independently.、Test、Reuse
function UserProfile({ userId }) { /* ... */ }
function UserEditForm({ userId }) { /* ... */ }
function UserPosts({ userId }) { /* ... */ }
▶ Try it Yourself

Output:

TEXT 📖 Display only
Before: 100+ line monolith. After: <UserPage> composes <UserProfile>, <UserEditForm>, <UserPosts> — each independent, testable, reusable

(1) Rules of Thumb for Splitting

BASH
If a component exceeds 150 lines
A function that exceeds 30 lines    
One render Return more than 10 JSX Element
Props More than 5

→ Consider a split!


5. Render Props pattern

Render Props is another composition pattern—it allows a parent component to control what a child component renders through a function prop.

JSX
// ============================================
// Example:Render Props——Mouse Position Tracker
// ============================================

// Child component:Responsible only for"Track Mouse Position",Don't care"How to display"
function MouseTracker({ render }) {
  const [position, setPosition] = React.useState({ x: 0, y: 0 })

  function handleMouseMove(e) {
    setPosition({ x: e.clientX, y: e.clientY })
  }

  return (
    <div style={{ height: '300px', border: '1px solid #ddd' }} onMouseMove={handleMouseMove}>
      {/* Let the parent component determine how to render the mouse position */}
      {render(position)}
    </div>
  )
}

// Parent Component:Decision"How to display"
function App() {
  return (
    <div>
      <h2>Render Props Example</h2>

      {/* Usage 1:Display Coordinates */}
      <MouseTracker render={({ x, y }) => (
        <div style={{ padding: '20px' }}>
          <p>Mouse Position:({x}, {y})</p>
        </div>
      )} />

      {/* Usage 2:Display Coordinate Indicator */}
      <MouseTracker render={({ x, y }) => (
        <div style={{
          position: 'absolute', left: x, top: y,
          width: '20px', height: '20px',
          backgroundColor: '#1890ff', borderRadius: '50%',
          transform: 'translate(-50%, -50%)',
          pointerEvents: 'none'
        }} />
      )} />
    </div>
  )
}
▶ Try it Yourself

Note: Since the introduction of React Hooks, most use cases for Render Props can be replaced with custom hooks (which are more concise). Render Props are commonly found in legacy code.



6. Composition vs. Inheritance

The React team explicitly states: Don't use inheritance. Use composition.

JSX
// ❌ Inheritance Patterns(Don't write it that way.)
class BaseButton extends React.Component {
  // ...Overriding Methods After Inheritance in a Subclass,React Not recommended
}
class PrimaryButton extends BaseButton { }

// ✅ Combination Mode(The Correct Way to Do It)
function Button({ variant = 'default', children, ...props }) {
  const styles = {
    primary: { backgroundColor: '#1890ff', color: 'white' },
    danger: { backgroundColor: '#ff4d4f', color: 'white' },
    default: { backgroundColor: '#f0f0f0', color: '#333' }
  }
  return <button style={styles[variant]} {...props}>{children}</button>
}

// Through Props Control Variants,rather than through inheritance
<Button variant="primary">Main Buttons</Button>
<Button variant="danger">Danger Button</Button>
<Button variant="default">Default Button</Button>
▶ Try it Yourself
Dimension Combination (Recommended) Inheritance (Not Recommended)
Flexibility High—Freely combinable via props and children Low—Fixed inheritance chain
Testability High—Each component can be tested independently Low—Depends on the parent classe's implementation
Type Safety Good—Props are explicitly typed Poor—Overriding methods is prone to errors


7. Complete Example: A Reusable Dashboard Card System

JSX
// ============================================
// Complete Example:Dashboard Card System
// Features:Combination Mode + children + Props Integrated Use of
// ============================================

// ---- 1. Basic Components:Card Holder ----
function DashboardCard({ title, icon, action, children, width = '300px' }) {
  return (
    <div style={{
      border: '1px solid #f0f0f0',
      borderRadius: '8px',
      width,
      backgroundColor: 'white',
      boxShadow: '0 2px 8px rgba(0,0,0,0.08)'
    }}>
      {/* Card Header */}
      <div style={{
        display: 'flex', justifyContent: 'space-between',
        alignItems: 'center', padding: '16px',
        borderBottom: '1px solid #f0f0f0'
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
          {icon && <span>{icon}</span>}
          <h3 style={{ margin: 0, fontSize: '16px' }}>{title}</h3>
        </div>
        {action && <div>{action}</div>}
      </div>

      {/* Card Content Slot */}
      <div style={{ padding: '16px' }}>
        {children}
      </div>
    </div>
  )
}

// ---- 2. Statistics Card ----
function StatCard({ label, value, trend, color = '#1890ff' }) {
  return (
    <div style={{ textAlign: 'center', padding: '8px 0' }}>
      <p style={{ color: '#666', fontSize: '14px', margin: '0 0 8px 0' }}>{label}</p>
      <p style={{ fontSize: '32px', fontWeight: 'bold', color, margin: '0 0 8px 0' }}>{value}</p>
      {trend !== undefined && (
        <p style={{
          color: trend >= 0 ? '#52c41a' : '#ff4d4f',
          fontSize: '14px', margin: 0
        }}>
          {trend >= 0 ? '↑' : '↓'} {Math.abs(trend)}%
        </p>
      )}
    </div>
  )
}

// ---- 3. List Cards ----
function ListCard({ title, icon, items, renderItem, action, emptyText = 'No data available' }) {
  return (
    <DashboardCard title={title} icon={icon} action={action}>
      {items.length === 0 ? (
        <p style={{ textAlign: 'center', color: '#999', padding: '20px' }}>{emptyText}</p>
      ) : (
        items.map((item, index) => (
          <div key={item.id} style={{
            padding: '8px 0',
            borderBottom: index < items.length - 1 ? '1px solid #f5f5f5' : 'none'
          }}>
            {renderItem(item)}
          </div>
        ))
      )}
    </DashboardCard>
  )
}

// ---- 4. Usage:Complete Dashboard ----
function Dashboard() {
  const stats = [
    { id: 'users', label: 'Total Users', value: '12,846', trend: 12 },
    { id: 'orders', label: 'Today's Orders', value: '328', trend: -3 },
    { id: 'revenue', label: 'Monthly Income', value: '$89,200', trend: 25 },
    { id: 'visits', label: 'Visits Today', value: '4,621', trend: 8 }
  ]

  const recentOrders = [
    { id: 1, customer: 'Alice', product: 'React Tutorial', amount: 89, status: 'Completed' },
    { id: 2, customer: 'Bob', product: 'TypeScript Getting Started', amount: 59, status: 'Processing...' },
    { id: 3, customer: 'Charlie', product: 'Node.js Real-World Experience', amount: 129, status: 'Completed' }
  ]

  return (
    <div>
      <h2>📊 Management Dashboard</h2>

      {/* Statistical Card Rows(Grid Layout) */}
      <div style={{ display: 'flex', gap: '16px', marginBottom: '24px', flexWrap: 'wrap' }}>
        <DashboardCard title="Data Overview" icon="📈" width="100%">
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}>
            {stats.map(s => (
              <StatCard key={s.id} {...s} />
            ))}
          </div>
        </DashboardCard>
      </div>

      {/* List of Recent Orders */}
      <div style={{ display: 'flex', gap: '16px' }}>
        <ListCard
          title="Recent Orders"
          icon="📋"
          items={recentOrders}
          action={<button style={btnStyle}>View All</button>}
          renderItem={order => (
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
              <div>
                <p style={{ margin: 0, fontWeight: 'bold' }}>{order.customer}</p>
                <p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '13px' }}>{order.product}</p>
              </div>
              <div style={{ textAlign: 'right' }}>
                <p style={{ margin: 0, color: '#ff4d4f' }}>${order.amount}</p>
                <span style={{
                  fontSize: '12px', padding: '2px 6px', borderRadius: '4px',
                  backgroundColor: order.status === 'Completed' ? '#f6ffed' : '#fff7e6',
                  color: order.status === 'Completed' ? '#52c41a' : '#faad14'
                }}>
                  {order.status}
                </span>
              </div>
            </div>
          )}
        />

        {/* Custom Cards:Use directly children */}
        <DashboardCard title="Quick Actions" icon="⚡" width="300px">
          <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
            <button style={actionBtnStyle}>📝 Create a New Order</button>
            <button style={actionBtnStyle}>👤 Add User</button>
            <button style={actionBtnStyle}>📊 Generate Reports</button>
            <button style={actionBtnStyle}>⚙️ System Settings</button>
          </div>
        </DashboardCard>
      </div>
    </div>
  )
}

const btnStyle = {
  padding: '4px 12px', border: '1px solid #d9d9d9',
  borderRadius: '4px', backgroundColor: 'white',
  cursor: 'pointer', fontSize: '12px'
}

const actionBtnStyle = {
  width: '100%', padding: '10px', border: 'none',
  borderRadius: '4px', backgroundColor: '#f5f5f5',
  cursor: 'pointer', textAlign: 'left', fontSize: '14px',
  transition: 'background-color 0.2s'
}
// Hover effects are enabled via onMouseEnter/Leave Add,Omitted here

Expected Output: A complete Dashboard page featuring a data overview card (with 4 metrics) at the top, a list of recent orders on the lower left, and a group of quick-action buttons on the lower right. All cards are built using the composition pattern.


▶ Example 3: Render Props—Sortable Tables

Output:

TEXT 📖 Display only
Sortable table: click column headers to sort asc/desc. Data rows reorder with sort direction indicator
JSX
function SortableTable({ data, columns, renderCell }) {
  const [sortKey, setSortKey] = useState(null)
  const [sortDir, setSortDir] = useState('asc')

  const sorted = useMemo(() => {
    if (!sortKey) return data
    return [...data].sort((a, b) => {
      const diff = a[sortKey] > b[sortKey] ? 1 : a[sortKey] < b[sortKey] ? -1 : 0
      return sortDir === 'asc' ? diff : -diff
    })
  }, [data, sortKey, sortDir])

  function handleSort(key) {
    if (sortKey === key) setSortDir(d => d === 'asc' ? 'desc' : 'asc')
    else { setSortKey(key); setSortDir('asc') }
  }

  return (
    <table border={1} cellPadding={8} style={{ borderCollapse: 'collapse', width: '100%' }}>
      <thead>
        <tr>
          {columns.map(col => (
            <th key={col.key} onClick={() => handleSort(col.key)} style={{ cursor: 'pointer' }}>
              {col.label} {sortKey === col.key ? (sortDir === 'asc' ? '↑' : '↓') : ''}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {sorted.map((row, i) => (
          <tr key={i}>{columns.map(col => <td key={col.key}>{renderCell(row, col.key)}</td>)}</tr>
        ))}
      </tbody>
    </table>
  )
}

function App() {
  const users = [
    { name: 'Alice', score: 92, role: 'Admin' },
    { name: 'Bob', score: 85, role: 'Editor' },
    { name: 'Charlie', score: 78, role: 'Viewer' },
  ]
  const cols = [{ key: 'name', label: 'Name' }, { key: 'score', label: 'Score' }, { key: 'role', label: 'Role' }]

  return (
    <div style={{ maxWidth: 500, margin: '0 auto' }}>
      <h3>Sortable Users</h3>
      <SortableTable data={users} columns={cols}
        renderCell={(row, key) => key === 'score' ? `${row[key]} pts` : row[key]} />
    </div>
  )
}

Output:

TEXT 📖 Display only
Sortable table: Alice 92pts, Bob 85pts, Charlie 78pts. Click headers to sort ↑/↓. Scores shown as "X pts".

▶ Example 4: Layout Combinations—Multi-Region Layouts

Output:

TEXT 📖 Display only
Layout with Header, Sidebar, Main, Footer regions. Composition pattern for flexible page structure
JSX
function AppLayout({ sidebar, header, content, footer }) {
  return (
    <div style={{ display: 'flex', minHeight: '100vh' }}>
      {sidebar && <aside style={{ width: 200, background: '#f5f5f5', padding: 16 }}>{sidebar}</aside>}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
        {header && <header style={{ padding: '12px 16px', background: '#1890ff', color: 'white' }}>{header}</header>}
        <main style={{ flex: 1, padding: 16 }}>{content}</main>
        {footer && <footer style={{ padding: '8px 16px', background: '#fafafa', fontSize: 12, color: '#999' }}>{footer}</footer>}
      </div>
    </div>
  )
}

function Dashboard() {
  return (
    <AppLayout
      sidebar={<nav><p>Menu</p><ul><li>Home</li><li>Settings</li></ul></nav>}
      header={<h2 style={{ margin: 0 }}>Dashboard</h2>}
      content={<p>Welcome back, Alice! You have 5 new tasks.</p>}
      footer={<span>© 2026 SaaS App</span>}
    />
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Layout with Header, Sidebar, Main, Footer regions. Composition pattern for flexible page structure

▶ Example 5: Composition vs. Inheritance—Popup Component

Output:

TEXT 📖 Display only
Subheading: "Dashboard". Displays: "Menu". List: Home, Settings
JSX
function Modal({ isOpen, onClose, title, children, footer }) {
  if (!isOpen) return null
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}
      onClick={onClose}>
      <div style={{ background: 'white', borderRadius: 8, padding: 24, minWidth: 320, maxWidth: 500 }}
        onClick={e => e.stopPropagation()}>
        <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
          <h3 style={{ margin: 0 }}>{title}</h3>
          <button onClick={onClose} style={{ border: 'none', background: 'none', cursor: 'pointer', fontSize: 18 }}>✕</button>
        </div>
        <div style={{ marginBottom: 16 }}>{children}</div>
        {footer && <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>{footer}</div>}
      </div>
    </div>
  )
}

function ConfirmModal({ isOpen, onConfirm, onCancel, message }) {
  return (
    <Modal isOpen={isOpen} onClose={onCancel} title="Confirm"
      footer={<>
        <button onClick={onCancel} style={{ padding: '6px 16px', cursor: 'pointer' }}>Cancel</button>
        <button onClick={onConfirm} style={{ padding: '6px 16px', background: '#ff4d4f', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>Confirm</button>
      </>}>
      <p>{message}</p>
    </Modal>
  )
}

function FormModal({ isOpen, onClose }) {
  const [value, setValue] = useState('')
  return (
    <Modal isOpen={isOpen} onClose={onClose} title="New Item"
      footer={<button onClick={() => { alert(value); onClose() }} style={{ padding: '6px 16px', background: '#1890ff', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>Submit</button>}>
      <input value={value} onChange={e => setValue(e.target.value)} placeholder="Enter name" style={{ width: '100%', padding: 8, borderRadius: 4 }} />
    </Modal>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Modal system: ConfirmModal (Cancel/Confirm, red confirm), FormModal (text input + Submit). Click overlay → closes. Composition over inheritance.

❓ FAQ

Q When should you use children, and when should you use Props?
A children is used for container components with "variable content areas" (such as modals, cards, and layouts). Props is used for components with "configurable content" (such as button colors and title text). Simply put: use children for structural elements and Props for configuration.
Q How do I choose between Render Props and Hooks?
A For new code, prioritize custom Hooks. Render Props are suitable for scenarios that involve both state logic and UI structure (such as MouseTracker, which requires a placeholder DOM). Hooks are suitable for scenarios that involve only state logic (such as useMousePosition, which only returns coordinates and does not render any UI). React officially recommends Hooks.
Q What should I do if there are too many props?
A This indicates that the component has too many responsibilities and needs to be split up. Recommendation: Group the props so that each group corresponds to the responsibilities of a child component. For example, if a UserForm component has 12 props, break it down into three child components: UserBasicInfo (name, email, phone number), UserAddress (province, city, district, detailed address), and UserSettings (role, permissions).
Q Which is better, Render Props or Hooks?
A Hooks are the better choice. Render Props were the reusability pattern used in React prior to version 16.7, requiring nested callback functions, which led to “callback hell.” Hooks allow you to reuse logic with just useXxx() a single line of code, resulting in flatter, more readable code. Render Props only have an advantage when the reused logic needs to “render a piece of UI” (such as when a tooltip component needs to know the position of its child elements).

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Create a Badge component that accepts count (a number) and children (content), and displays a red superscript number in the upper-right corner.
  2. Advanced Exercise (Difficulty ⭐⭐): Create a Tabs component and use the Composite Pattern to implement label switching: <Tabs><TabPanel label="Tab1">inside content 1</TabPanel><TabPanel label="Tab2">inside content 2</TabPanel></Tabs>.
  3. Challenge (Difficulty: ⭐⭐⭐): Create a Table component using the Render Props pattern: <Table data={users} columns={[{ title: 'Name', render: u => u.name }, { title: 'Age', render: u => u.age }]} />, and support sorting and zebra highlighting.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏