Splitting Components — Props and Event Callbacks

Componentization is the core design philosophy of React. In this chapter, you will learn to split pages into reusable small components and implement parent-child communication through Props and callback functions.


Why split components?

App.jsx keeps getting longer, with hundreds of lines crammed together, making it hard to maintain and reuse.

Principles for splitting components:Each component is responsible for one thing, and they combine to accomplish complex functionality.。

What granularity is appropriate?

Page areaWhether to split into a componentReason
Navigation barYesEvery page needs it; independent functionality
Article cardYesEvery article in the list has the same structure
Category filter button groupYesHas its own state and interactions
Simple wrapper containernoOnly for layout, no independent behavior

Parent to child: Props

In React,Props are function parameters— when the parent component calls a child component, it passes data in through attributes.

Example

// File path: src/components/BlogCard.jsx
// Props is the first parameter of the function, usually used with destructuring
function BlogCard({ title, summary, date, category }) {
  return (
    <div className="card">
      <span className="tag">{category}</span>
      <h3>{title}</h3>
      <p>{summary}</p>
      <span className="date">{date}</span>
    </div>
  )
}

export default BlogCard

The parent component passes data in like writing HTML attributes:

Example

// File path: src/App.jsx
import BlogCard from './components/BlogCard'

function App() {
  const articles = [
    { id: 1, title: 'React Introduction', summary: 'Learn React from Scratch', date: '2024-05-10', category: 'React' },
    { id: 2, title: 'Promise in Detail', summary: 'Understand Async', date: '2024-05-08', category: 'JavaScript' },
  ]

  return (
    <div className="article-grid">
      {articles.map(article => (
        <BlogCard
          key={article.id}
          title={article.title}
          summary={article.summary}
          date={article.date}
          category={article.category}
        />
      ))}
    </div>
  )
}

Props areunidirectional data flow: data flows from the parent component to the child component. A child component cannot modify the Props it receives. If modification is needed, the parent component should handle it through a callback function.


Child to parent: callback function

When a child component needs to notify the parent component "something happened", the parent component needs to pass acallback functionas Props.

Example

// File path: src/components/CategoryFilter.jsx
function CategoryFilter({ categories, activeCategory, onCategoryChange }) {
  return (
    <div className="filter-bar">
      {categories.map(cat => (
        <button
          key={cat}
          className={activeCategory === cat ? 'active' : ''}
          // Call the callback function passed from the parent component when clicked
          onClick={() => onCategoryChange(cat)}
        >
          {cat}
        </button>
      ))}
    </div>
  )
}

export default CategoryFilter

The parent component passes the callback function:

Example

import { useState } from 'react'
import CategoryFilter from './components/CategoryFilter'

function App() {
  const [activeCategory, setActiveCategory] = useState('All')
  const categories = ['All', 'React', 'JavaScript', 'CSS']

  // Callback function: respond to the child component's click event
  function handleCategoryChange(cat) {
    setActiveCategory(cat)
  }

  return (
    <CategoryFilter
      categories={categories}
      activeCategory={activeCategory}
      onCategoryChange={handleCategoryChange}
    />
  )
}

Communication comparison between React and Vue3

ScenarioVue3React
Parent to childProps(defineProps)Props (function parameters)
Child to parentemit eventProps callback function
Data directionOne-wayOne-way

Vue3 uses the emit mechanism, React uses callback functions — the essence is the same, just the syntax differs.


Props destructuring and default values

Example

// Props with default values
function BlogCard({
  title = 'Untitled Article',        // ES6 destructuring default values
  summary = 'No Summary Yet',
  date = '',
  category = 'Uncategorized'
}) {
  return (
    <div className="card">
      <span className="tag">{category}</span>
      <h3>{title}</h3>
      <p>{summary}</p>
      {date && <span className="date">{date}</span>}
    </div>
  )
}

Hands-on: split out three components

The component structure of the blog project is as follows:

src/
├── components/
│   ├── NavBar.jsx       # 顶部导航栏
│   ├── BlogCard.jsx     # 单篇文章卡片
│   └── CategoryFilter.jsx  # 分类筛选按钮组
└── App.jsx

NavBar.jsx

Example

// File path: src/components/NavBar.jsx
import '../App.css'

function NavBar() {
  return (
    <header className="navbar">
      <a href="/" className="logo">EXAMPLE Blog</a>
      <nav>
        <a href="/">Home</a>
        <a href="#">About</a>
      </nav>
    </header>
  )
}

export default NavBar

App.jsx — Integrating all components

Example

// File path: src/App.jsx
import { useState, useMemo } from 'react'
import NavBar from './components/NavBar'
import BlogCard from './components/BlogCard'
import CategoryFilter from './components/CategoryFilter'
import './App.css'

function App() {
  const [articles] = useState([
    { id: 1, title: 'The Complete Guide to Getting Started with React', summary: 'Learning React Hooks from Scratch', date: '2024-05-10', category: 'React' },
    { id: 2, title: 'JS Asynchronous Programming In Detail', summary: Understand Promise and async/await, date: '2024-05-08', category: 'JavaScript' },
    { id: 3, title: Practical CSS Grid Layout, summary: 'Implement Responsive Layout with Grid', date: '2024-05-05', category: 'CSS' },
    { id: 4, title: 'React Hooks Deep Dive', summary: 'Deep understanding of useState and useEffect', date: '2024-05-03', category: 'React' },
    { id: 5, title: 'The Complete Guide to Flexbox', summary: 'Learn Flex Layout in One Article', date: '2024-05-01', category: 'CSS' },
  ])

  const [activeCategory, setActiveCategory] = useState('All')

  const categories = useMemo(() => {
    const cats = articles.map(a => a.category)
    return ['All', ...new Set(cats)]
  }, [articles])

  const filteredArticles = useMemo(() => {
    if (activeCategory === 'All') return articles
    return articles.filter(a => a.category === activeCategory)
  }, [articles, activeCategory])

  return (
    <div className="app">
      <NavBar />

      <main className="container">
        <h2 className="section-title">Latest articles</h2>

        {/* Child component: receives props (categories, activeCategory) and callback (onCategoryChange) */}
        <CategoryFilter
          categories={categories}
          activeCategory={activeCategory}
          onCategoryChange={setActiveCategory}
        />

        <p className="result-info">common{filteredArticles.length}article</p>

        {filteredArticles.length === 0 ? (
          <p className="empty-tip">No articles under this category yet</p>
        ) : (
          <div className="article-grid">
            {filteredArticles.map(article => (
              <BlogCard
                key={article.id}
                title={article.title}
                summary={article.summary}
                date={article.date}
                category={article.category}
              />
            ))}
          </div>
        )}
      </main>

      <footer className="footer">
        <p>© 2024 EXAMPLE Blog. Powered by React.</p>
      </footer>
    </div>
  )
}

export default App

Chapter summary

In this chapter, you mastered the core of React component communication: parent to child via Props (function parameters), child to parent via Props callback functions.

The three components of the blog project — NavBar, BlogCard, and CategoryFilter — are in place, and the code structure is much clearer.

Other extensions