2.6 — Component Architecture Principles: Quick Revision
Compact cheat sheet. Print-friendly.
How to use this material:
- Skim before an interview or exam
- Use as a refresher after reading the full sub-topics
- If anything is unclear, go back to the relevant sub-topic file
- Test yourself: cover the right column and recall from the left
2.6.a — Single Responsibility Principle
| Concept | Summary |
|---|
| SRP definition | A component should have one reason to change |
| "One thing" means | One cohesive purpose — not one HTML element |
| The "and" test | If describing the component needs "and" between unrelated ideas → split |
| Reason to change test | List all reasons → more than 3 = split |
Code Smells for SRP Violation
| Smell | Threshold |
|---|
| useState calls | 7+ → too many |
| useEffect calls | 3+ → probably separate concerns |
| File length | 200+ lines → check; 400+ → definitely split |
| Comment dividers | {/* --- Section --- */} → each should be a component |
| Mixed abstraction | High-level components mixed with raw HTML → extract layout |
Separation Layers
Data Layer: custom hooks (useUsers, useProduct)
Logic Layer: utility functions (calculatePrice, validateEmail)
Effect Layer: custom hooks (useDocumentTitle, usePageView)
UI Layer: components (ProductCard, UserList) — props only
2.6.b — Smart vs Dumb Components
| Smart (Container) | Dumb (Presentational) |
|---|
| Fetches data, manages state | Receives data via props |
| Has side effects (useEffect) | No side effects |
| Minimal DOM output | DOM-heavy (HTML/CSS) |
| Not reusable | Very reusable |
| Hard to test | Easy to test |
Allowed in Presentational Components
✅ UI state: isOpen, activeTab, hoveredIndex, tooltipPosition
❌ Data state: users, notifications, cartItems
Modern Evolution
2015: Container component + Presentational component (two files)
2019: Custom hook + Component (hook replaces container)
2023: Server Component + Client Component (Next.js)
Rule: Design system components = ALWAYS presentational
2.6.c — Lifting State Up
Three Steps
1. Remove state from child
2. Add state to nearest common parent
3. Pass state down as props + setter via callbacks
Core Principles
| Principle | Meaning |
|---|
| Single source of truth | One component owns state, others receive via props |
| Data flows down | Parent → child via props |
| Events flow up | Child → parent via callbacks |
| Derive, don't duplicate | Calculate values from state — don't maintain redundant state |
When to Lift vs Not
| Lift | Don't Lift |
|---|
| Siblings need same data | Only one component uses it |
| Data must stay in sync | Pure UI state (hover, isOpen) |
| Parent derives values | No sibling depends on it |
Performance Mitigations
React.memo → wrap expensive siblings that don't use changed state
Sub-parent → wrap related components to scope re-renders
Composition → pass children as props (created in different scope)
2.6.d — Prop Drilling Problem
Definition
Passing props through intermediate components that don't use them.
Prop Drilling Score
| Score (passthroughs) | Assessment |
|---|
| 0 (direct parent-child) | Not drilling |
| 1 | Mild — fine |
| 2 | Moderate — consider alternatives |
| 3+ | Severe — refactor |
Solutions Summary
| Solution | When to Use | Complexity |
|---|
| Composition (children/slots) | Layout slots, 1-2 levels | Low |
| Context API | App-wide, infrequent changes | Medium |
| Zustand | Frequent updates, performance | Medium |
| URL state | Shareable state (search, filters) | Low |
| Restructure tree | Poor tree design | Low |
Decision Quick-Reference
Adjacent siblings → lift state (props)
3+ levels, rare changes → Context
3+ levels, frequent changes → Zustand
Should be in URL → useSearchParams
2.6.e — Component Composition
Composition Patterns
| Pattern | Description | Example |
|---|
| children | Content between tags | <Card>{content}</Card> |
| Named slots | Multiple insertion points | <Layout header={...} sidebar={...}> |
| Specialization | Pre-fill props on generic | SuccessAlert = Alert + type="success" |
| Compound | Shared implicit state | <Tabs><Tabs.Tab /><Tabs.Panel /></Tabs> |
| Render props | Function-as-child | <Fetch>{({data}) => ...}</Fetch> |
Polymorphic as | Render as any element | <Box as="section"> |
Composition vs Inheritance
Inheritance: "is-a" → rigid hierarchy, fragile base class
Composition: "has-a" → flexible, mix and match, React recommended
children Can Be
String: <Card>Hello</Card>
Element: <Card><p>Hi</p></Card>
Array: <Card><h2/><p/></Card>
Component: <Card><UserProfile /></Card>
Function: <Card>{(data) => <div>{data}</div>}</Card>
Nothing: <Card />
Key Libraries Using Composition
- Radix UI:
<Dialog.Root> / <Dialog.Content> (compound)
- React Router:
<Routes><Route element={...} /> (declarative)
- shadcn/ui:
<Card><CardHeader /><CardContent /> (compound)
Master Cheat Sheet
SRP: One component = one reason to change. Split if 3+ reasons.
Smart/Dumb: Separate data logic from UI rendering.
Lift State: Shared state → nearest common parent → pass via props.
Prop Drill: 3+ passthrough levels → use Context/Zustand/composition.
Composition: children + slots + specialization + compound components.