Design Patterns
Common design patterns in frontend development
What you'll learn
Recognize common frontend design patterns: Observer, Module, Factory, Singleton
Implement the Observer pattern for event-driven communication
Use the Module pattern to encapsulate private state
Understand when each pattern is appropriate and when it adds unnecessary complexity
Compare JavaScript's module system to classical design pattern implementations
Reusable solutions to common problems.
Common Patterns
| Pattern | Use Case |
|---|---|
| Compound Components | Flexible reusable UI (Select, Tabs) |
| Render Props | Share logic with flexible rendering |
| Custom Hooks | Extract reusable stateful logic |
| Higher-Order Components | Cross-cutting concerns (legacy) |
| Provider Pattern | Global state (Context) |
Compound Components
function Tabs({ children }: { children: React.ReactNode }) {
const [active, setActive] = useState(0)
return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>
}
Tabs.List = function List({ children }: { children: React.ReactNode }) { ... }
Tabs.Tab = function Tab({ index, children }: { index: number; children: React.ReactNode }) { ... }
Tabs.Panel = function Panel({ index, children }: { index: number; children: React.ReactNode }) { ... }
// Usage
<Tabs>
<Tabs.List>
<Tabs.Tab index={0}>Tab 1</Tabs.Tab>
<Tabs.Tab index={1}>Tab 2</Tabs.Tab>
</Tabs.List>
<Tabs.Panel index={0}>Content 1</Tabs.Panel>
<Tabs.Panel index={1}>Content 2</Tabs.Panel>
</Tabs>
Next Steps
Key Takeaways
Design patterns provide reusable solutions to common software design problems
The Observer pattern enables event-driven communication between decoupled components
The Module pattern encapsulates private state using closures and IIFEs
Modern JavaScript modules (ESM) replace many classical patterns with native language features
Patterns are tools, not rules — inappropriate abstraction adds complexity without benefit