Design Patterns
Custom hooks, compound components, render props, HOCs, and controlled vs uncontrolled components.
🧑🏫 Sabse pehle — simple mein samjho#
Design patterns matlab React mein logic ko dobara-dobara use karne ke saaf-suthre tarike. Custom hook ek recipe jaisa hai — ek baar likho (jaise "chai banane ka tarika") aur har jagah use karo. Compound components aise components hote hain jo ek saath mil ke kaam karte, bilkul TV remote aur TV set ki tarah — alag-alag dikhte hain par ek doosre ke bina adhoore. Zomata ka cart aur uska total price ek hi family ke parts ki tarah aapas mein baat karte — wahi idea hai. In patterns se code repeat nahi hota aur padhne mein aasaan rehta.
// Custom hook = reusable recipe
function useCounter(start = 0) {
const [count, setCount] = useState(start);
const inc = () => setCount((c) => c + 1); // logic ek jagah
return { count, inc };
}
function Likes() {
const { count, inc } = useCounter(); // kahin bhi use karo
return <button onClick={inc}>👍 {count}</button>;
}
Yaad rakho: custom hook = logic ki recipe; compound components = milke kaam karne wali family.
Overview#
Modern React composes behavior with hooks; older codebases used render props and HOCs to share logic before hooks existed. Know all of them — interviews probe the legacy ones and expect you to explain why hooks replaced them.
flowchart TD
A["Share logic between components"] --> B["Custom hook (modern default)"]
A --> C["Render props (legacy)"]
A --> D["HOC (legacy)"]
E["Share structure/UI composition"] --> F["Compound components"]
Custom hooks — the modern default 🟢#
Extract stateful logic into a use* function. It's just a function calling other hooks; each caller gets its own isolated state. Replaces both render props and HOCs for logic reuse without wrapper nesting.
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn((v) => !v), []);
return [on, toggle];
}
function Panel() {
const [open, toggle] = useToggle();
return <button onClick={toggle}>{open ? "Hide" : "Show"}</button>;
}
Compound components#
Components that share implicit state via Context and only make sense together (like <select>/<option>). The parent owns state; children read it through context. Gives a flexible, declarative API without prop-drilling.
import { createContext, useContext, useState } from "react";
const TabsContext = createContext(null);
function Tabs({ children, defaultValue }) {
const [active, setActive] = useState(defaultValue);
return (
<TabsContext.Provider value={{ active, setActive }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function Tab({ value, children }) {
const { active, setActive } = useContext(TabsContext);
return (
<button aria-selected={active === value} onClick={() => setActive(value)}>
{children}
</button>
);
}
function TabPanel({ value, children }) {
const { active } = useContext(TabsContext);
return active === value ? <div role="tabpanel">{children}</div> : null;
}
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;
// Usage — clean, self-describing API:
<Tabs defaultValue="a">
<Tabs.Tab value="a">First</Tabs.Tab>
<Tabs.Tab value="b">Second</Tabs.Tab>
<Tabs.Panel value="a">Panel A</Tabs.Panel>
<Tabs.Panel value="b">Panel B</Tabs.Panel>
</Tabs>
🎯 Consumers arrange the pieces freely while shared state stays encapsulated — the key selling point over a monolithic <Tabs items={[...]}/> prop API.
Render props (legacy)#
Pass a function as a prop (often children) that receives state and returns UI. Solved logic reuse pre-hooks but causes "wrapper hell" / nested callbacks.
function Mouse({ children }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}>
{children(pos)}
</div>
);
}
<Mouse>{({ x, y }) => <p>{x}, {y}</p>}</Mouse>
HOCs (legacy)#
A higher-order component is a function Component -> Component that injects props. Still seen in libraries (connect, withRouter). Downsides: prop collisions, wrapper nesting, unclear data origin.
function withUser(Wrapped) {
return function WithUser(props) {
const user = useAuth();
return <Wrapped {...props} user={user} />;
};
}
const ProfileWithUser = withUser(Profile);
⚠️ Hooks superseded HOCs and render props for logic sharing — they avoid extra tree nesting and make data flow explicit. Prefer a custom hook.
Container / presentational (legacy)#
Split "smart" containers (fetch/state) from "dumb" presentational components (props → UI). Hooks made the strict split less necessary since any component can hold logic via hooks, but the separation of concerns idea still guides good structure.
Controlled vs uncontrolled components 🎯#
| Controlled | Uncontrolled | |
|---|---|---|
| Source of truth | React state | The DOM |
| Read value | From state | Via ref |
| Set value | value + onChange |
defaultValue, DOM |
| Use when | Validation, formatting, dependent fields | Simple/one-shot, file inputs, perf |
// Controlled
const [name, setName] = useState("");
<input value={name} onChange={(e) => setName(e.target.value)} />;
// Uncontrolled
const ref = useRef();
<input defaultValue="hi" ref={ref} />; // read ref.current.value on submit
⚠️ <input type="file"> is always uncontrolled (read-only value). Switching an input between controlled/uncontrolled at runtime warns — pick one.
Interview Q&A#
Q1. Why did custom hooks replace render props and HOCs? They share stateful logic without adding wrapper components, avoiding nesting/"wrapper hell", prop collisions, and unclear data flow. Logic is a plain function each caller instantiates independently.
Q2. What are compound components and why use them?
Related components sharing implicit state via Context (e.g. Tabs/Tab/TabPanel). They give a flexible, declarative composition API and encapsulate shared state without prop-drilling.
Q3. Controlled vs uncontrolled inputs?
Controlled inputs derive their value from React state (value+onChange) — best for validation/formatting. Uncontrolled inputs let the DOM hold the value, read via ref — simpler and needed for file inputs.
Q4. What is a HOC and a downside? A function taking a component and returning an enhanced one that injects props. Downsides: wrapper nesting, prop-name collisions, and obscured data origin — reasons hooks replaced them.
Q5. Is container/presentational still relevant? The strict pattern is legacy since hooks let any component hold logic, but its underlying principle — separating data/behavior from pure rendering — still improves testability and reuse.