Episode 2 — React Frontend Architecture NextJS / 2.6 — Component Architecture Principles

2.6 — Component Architecture Principles: Quick Revision

Compact cheat sheet. Print-friendly.

How to use this material:

  1. Skim before an interview or exam
  2. Use as a refresher after reading the full sub-topics
  3. If anything is unclear, go back to the relevant sub-topic file
  4. Test yourself: cover the right column and recall from the left

2.6.a — Single Responsibility Principle

ConceptSummary
SRP definitionA component should have one reason to change
"One thing" meansOne cohesive purpose — not one HTML element
The "and" testIf describing the component needs "and" between unrelated ideas → split
Reason to change testList all reasons → more than 3 = split

Code Smells for SRP Violation

SmellThreshold
useState calls7+ → too many
useEffect calls3+ → probably separate concerns
File length200+ lines → check; 400+ → definitely split
Comment dividers{/* --- Section --- */} → each should be a component
Mixed abstractionHigh-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 stateReceives data via props
Has side effects (useEffect)No side effects
Minimal DOM outputDOM-heavy (HTML/CSS)
Not reusableVery reusable
Hard to testEasy 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

PrincipleMeaning
Single source of truthOne component owns state, others receive via props
Data flows downParent → child via props
Events flow upChild → parent via callbacks
Derive, don't duplicateCalculate values from state — don't maintain redundant state

When to Lift vs Not

LiftDon't Lift
Siblings need same dataOnly one component uses it
Data must stay in syncPure UI state (hover, isOpen)
Parent derives valuesNo 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
1Mild — fine
2Moderate — consider alternatives
3+Severe — refactor

Solutions Summary

SolutionWhen to UseComplexity
Composition (children/slots)Layout slots, 1-2 levelsLow
Context APIApp-wide, infrequent changesMedium
ZustandFrequent updates, performanceMedium
URL stateShareable state (search, filters)Low
Restructure treePoor tree designLow

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

PatternDescriptionExample
childrenContent between tags<Card>{content}</Card>
Named slotsMultiple insertion points<Layout header={...} sidebar={...}>
SpecializationPre-fill props on genericSuccessAlert = Alert + type="success"
CompoundShared implicit state<Tabs><Tabs.Tab /><Tabs.Panel /></Tabs>
Render propsFunction-as-child<Fetch>{({data}) => ...}</Fetch>
Polymorphic asRender 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.