Design Patterns
4 min readReact Design Patterns (Interview-Ready)
Patterns in React are mostly about composition (building features by combining small components) rather than inheritance.
Table of Contents
- Compound Components
- Controlled vs Uncontrolled Components
- Provider Pattern (Context)
- State Reducer Pattern
- Render Props
- Higher-Order Components (HOC)
- Container / Presentational Split
- Polymorphic Components (
asprop)
Compound Components
What it is: a parent component owns state and shares it with “subcomponents” via Context so you can write an expressive API like:
<Tabs defaultValue="profile">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="billing">Billing</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="profile">...</Tabs.Panel>
<Tabs.Panel value="billing">...</Tabs.Panel>
</Tabs>
Why it’s useful:
- Avoids prop drilling in complex UI widgets (tabs, accordion, dropdown, menu).
- Enables flexible composition while keeping state in one place.
- Improves readability: the JSX structure becomes the “API”.
✅ Good example: Compound Toggle
import React from 'react';
const ToggleContext = React.createContext(null);
function useToggleContext() {
const ctx = React.useContext(ToggleContext);
if (!ctx) throw new Error('Toggle.* must be used within <Toggle>');
return ctx;
}
export function Toggle({ defaultOn = false, children }) {
const [on, setOn] = React.useState(defaultOn);
const value = React.useMemo(() => ({ on, setOn }), [on]);
return <ToggleContext.Provider value={value}>{children}</ToggleContext.Provider>;
}
Toggle.On = function ToggleOn({ children }) {
const { on } = useToggleContext();
return on ? children : null;
};
Toggle.Off = function ToggleOff({ children }) {
const { on } = useToggleContext();
return on ? null : children;
};
Toggle.Button = function ToggleButton(props) {
const { on, setOn } = useToggleContext();
return (
<button type="button" aria-pressed={on} onClick={() => setOn((v) => !v)} {...props} />
);
};
❌ Bad example: Prop drilling
function Toggle({ on, setOn }) {
return (
<div>
<ToggleOn on={on} />
<ToggleOff on={on} />
<ToggleButton on={on} setOn={setOn} />
</div>
);
}
Best practices:
- Prefer Context + small subcomponents over
React.Children.map“magic”. - Fail fast: throw if a subcomponent is used outside its provider.
- Keep the context value stable (
useMemo) to reduce re-renders. - Provide controlled + uncontrolled support for reusable libraries (see below).
A: When multiple child components need shared state/behavior and you want an expressive, composable API (tabs, menu, accordion).
Controlled vs Uncontrolled Components
Controlled: parent owns state (value, onChange). Uncontrolled: component owns state (defaultValue) and notifies changes.
✅ Good example: controlled/uncontrolled API shape
function useControllableState({ value, defaultValue, onChange }) {
const [uncontrolled, setUncontrolled] = React.useState(defaultValue);
const isControlled = value !== undefined;
const current = isControlled ? value : uncontrolled;
const set = React.useCallback(
(next) => {
const nextValue = typeof next === 'function' ? next(current) : next;
if (!isControlled) setUncontrolled(nextValue);
onChange?.(nextValue);
},
[current, isControlled, onChange]
);
return [current, set];
}
A: Apps often start uncontrolled (simple), then move to controlled when state must be synchronized (forms, URL, server state).
Provider Pattern (Context)
Use Context to provide cross-cutting dependencies or shared UI state (theme, auth, feature flags, tabs state).
Best practices:
- Split contexts by “change frequency” (e.g.,
AuthStateContextvsAuthActionsContext) to reduce re-renders. - Memoize provider values:
useMemo(() => ({...}), [deps]). - Keep providers close to where they’re needed; avoid a “provider soup” at the root unless justified.
State Reducer Pattern
What it is: component manages state, but lets consumers override transitions via a reducer.
Why it’s useful:
- Makes reusable components more flexible without exposing internals.
- Allows apps to enforce rules (e.g., prevent toggle off in some cases).
function defaultToggleReducer(state, action) {
switch (action.type) {
case 'toggle':
return { ...state, on: !state.on };
default:
return state;
}
}
function useToggle({ defaultOn = false, reducer = defaultToggleReducer, onChange }) {
const [state, dispatch] = React.useReducer(reducer, { on: defaultOn });
const toggle = () => dispatch({ type: 'toggle' });
React.useEffect(() => {
onChange?.(state.on);
}, [onChange, state.on]);
return { on: state.on, toggle };
}
onChange only?A: When you need to customize logic (state transitions), not just observe state changes.
Render Props
What it is: pass a function as a child/prop to share state + behavior.
function Mouse({ children }) {
const [pos, setPos] = React.useState({ x: 0, y: 0 });
return (
<div onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}>
{children(pos)}
</div>
);
}
Trade-offs:
- ✅ Very flexible, explicit data flow
- ❌ Can create “JSX nesting” and re-render hot paths if not careful
Higher-Order Components (HOC)
What it is: a function that takes a component and returns an enhanced component.
const withAuth = (Component) => (props) => {
const user = useUser();
if (!user) return <Login />;
return <Component {...props} user={user} />;
};
Modern guidance: prefer hooks and composition for new code, but recognize HOCs in existing codebases.
Container / Presentational Split
Keep data fetching and state in a container, and keep UI rendering in a presentational component.
function UserCardContainer({ userId }) {
const { data, isLoading } = useUserQuery(userId);
return <UserCard user={data} loading={isLoading} />;
}
function UserCard({ user, loading }) {
if (loading) return <Spinner />;
return <div>{user.name}</div>;
}
Polymorphic Components (as prop)
Used in design systems to let a component render as different HTML tags while preserving styling and behavior.
function Text({ as: As = 'span', ...props }) {
return <As {...props} />;
}
Best practice: in TypeScript, make this type-safe (generic T extends ElementType) to preserve props of the chosen element.