Welcome back to our series on mastering React web components! In our previous post, we introduced the fundamentals of web components and why they're transforming modern web development. Now, it's time to dive deeper into structuring your React application with a component-based architecture that scales.
The Component Hierarchy: Shell, App, and Feature Components
When building React applications, organizing your components into a clear hierarchy improves maintainability and scalability. Let's explore a three-tier approach that many production applications adopt:
Shell Components: The Application Framework
Shell components form the outermost layer of your application.
They:
- Define the overall layout structure
- Handle global navigation elements
- Manage application-wide state
- Are typically rendered once and persist throughout the user session
// AppShell.jsx
function AppShell() {
return (
<div className="app-container">
<Header />
<Sidebar />
<main className="content-area">
<Outlet /> {/* React Router outlet for rendering nested routes */}
</main>
<Footer />
</div>
);
}
Shell components rarely contain business logic. Instead, they provide the scaffolding for your application and ensure consistent layout across different views.
Main App Components: The Feature Containers
Main app components serve as containers for specific application sections or views. They :
- Map to major routes or views
- Orchestrate data fetching and state management for their section
- Pass data down to feature components
- Handle section-specific user interactions and events
// DashboardView.jsx
function DashboardView() {
const [userData, setUserData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Fetch user dashboard data
async function fetchData() {
setIsLoading(true);
try {
const data = await fetchDashboardData();
setUserData(data);
} catch (error) {
console.error("Failed to load dashboard", error);
} finally {
setIsLoading(false);
}
}
fetchData();
}, []);
return (
<div className="dashboard-container">
<DashboardHeader username={userData?.name} />
{isLoading ? (
<LoadingSpinner />
) : (
<>
<StatsSummary stats={userData?.stats} />
<RecentActivity activities={userData?.recentActivities} />
<Recommendations items={userData?.recommendations} />
</>
)}
</div>
);
}
Feature Components: The Building Blocks
Feature components are specialized, reusable components that represent distinct UI elements. They:
- Focus on a single responsibility
- Accept props for customization and data display
- Can maintain their own internal state when needed
- Should be highly reusable across different contexts
// StatsSummary.jsx
function StatsSummary({ stats }) {
return (
<div className="stats-grid">
{stats.map(stat => (
<StatCard
key={stat.id}
title={stat.title}
value={stat.value}
change={stat.change}
icon={stat.icon}
/>
))}
</div>
);
}
// StatCard.jsx
function StatCard({ title, value, change, icon }) {
const isPositive = change > 0;
return (
<div className="stat-card">
<div className="stat-icon">{icon}</div>
<h3 className="stat-title">{title}</h3>
<p className="stat-value">{value}</p>
<p className={`stat-change ${isPositive ? 'positive' : 'negative'}`}>
{isPositive ? '↑' : '↓'} {Math.abs(change)}%
</p>
</div>
);
}
Component Communication Patterns
With this hierarchy, data typically flows downward via props, while events bubble up through callback functions. For cross-component communication, consider:
- Prop drilling - For shallow hierarchies
- Context API - For shared state across multiple components
- State management libraries - For complex applications (Redux, Zustand, etc.)
Best Practices
- Single Responsibility: Each component should do one thing well
- Composition over Inheritance: Build complex components by composing simpler ones
- Consistent Props API: Standardize prop naming across similar components
- Component Documentation: Document expected props and behaviors
- Separation of Concerns: Keep data fetching logic separate from presentation
Next Steps
In our next post, we'll explore state management strategies for your React components, from local state to global state solutions. We'll compare different approaches and provide guidance on when to use each one.
Until then, try refactoring an existing React application using this shell/app/feature component structure, and notice how it improves your code organization and reusability!
Happy coding!