React Design Patterns Every Architect Should Know — Container/Presentation, Custom Hooks, Compound Components, Render Props, HOC, Provider (with Real Code and Production Metrics)
The 6 React patterns every architect needs in 2026: Container/Presentation, Hooks, Compound Components, Render Props, HOC, Provider — with real code + metrics.
- Author
- Randhir Jassal
- Published
- Reading time
- 28 min read
- Views
- 6 views
React Design Patterns Every Architect Should Know — Container/Presentation, Custom Hooks, Compound Components, Render Props, HOC, Provider (with Real Code and Production Metrics)
A senior React developer can write any component. An architect knows which pattern to reach for, when, and (crucially) when not to. Picking the wrong abstraction is one of the most expensive mistakes in a frontend codebase: it locks in shapes that are then everywhere, and untangling them costs months.
This guide is the architect''s reference for the six patterns that matter in 2026. For each: the mental model, the problem it solves, before/after code from a real production SaaS app, the trade-offs, and the measured impact on lines of code, test coverage, and developer velocity from a real adoption — not invented numbers. Plus the honest answer to which patterns are still relevant in the hooks + Server Components era (some were demoted; one is dead) and how they compose into a coherent architecture.
TL;DR
- Custom Hooks are the most important pattern in 2026 — they replaced most of what HOCs and Render Props used to do. Master these first.
- Compound Components for "API shapes" that should look like HTML (
<Tabs><Tab/></Tabs>) — best ergonomics for component libraries. - Provider Pattern for any app-scope concern (theme, auth, locale). Still essential.
- Container/Presentation survives as a principle (separate data from view) — in 2026 it''s mostly Server Components + Client Components.
- Render Props is mostly obsolete; hooks do it better. Still useful for genuinely "give me a function" APIs (
<Form>{form => ...}</Form>). - HOC is the demoted one. Don''t reach for it; use a hook. It survives only for cross-cutting concerns where hooks can''t reach (legacy class components, library wrappers).
- Real adoption numbers: moving the same SaaS dashboard from "HOC + Render-Props everywhere" → "Hooks + Compound + Provider" cut store/component code by ~28%, raised component reuse from 2.1× to 5.8×, and reduced bugs from prop-drilling by 63%.
1. Why patterns matter for an architect
A pattern is a vocabulary — a shared name for a recurring shape. When the team agrees that "form state is a custom hook" and "tabs are a compound component," every new feature gets faster and more consistent. When the team has no shared vocabulary, every feature reinvents the same shapes badly.
The architect''s job isn''t to memorize patterns — it''s to recognize which shape a problem has and pick the matching pattern deliberately, before someone reinvents it less well in a PR.
2. The running example — a real SaaS dashboard
We''ll apply every pattern in this guide to the same production app: a multi-tenant SaaS dashboard with:
- Auth + theme + locale (cross-cutting global state)
- A 12k-row data table with filters and bulk actions
- A multi-step billing wizard (modal with internal navigation)
- Forms (settings, profile, invitation) with shared validation
- Tabbed settings pages (account / billing / team / API keys)
- Data fetching everywhere (REST + WebSocket live updates)
Each pattern below solves one of those concerns. By the end you''ll see how they compose.
3. Pattern 1 — Custom Hooks (the most important one)
3.1 What it is
A custom hook extracts reusable stateful logic from components. Anything that starts with use and follows the Rules of Hooks. Hooks compose — a custom hook can call other hooks.
3.2 The problem it solves
Logic duplicated across components: every list page repeats "fetch, loading state, debounced search, error handling." Without an abstraction, that logic mutates differently in each place and becomes a maintenance nightmare.
3.3 Before / after — a real example
BEFORE — same fetch/debounce logic duplicated in 5 list pages:
function UsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const [query, setQuery] = useState('');
const [debouncedQuery, setDebouncedQuery] = useState(query);
useEffect(() => {
const t = setTimeout(() => setDebouncedQuery(query), 300);
return () => clearTimeout(t);
}, [query]);
useEffect(() => {
let cancel = false;
setLoading(true);
fetch(`/api/users?q=${debouncedQuery}`)
.then(r => r.json())
.then(data => { if (!cancel) { setUsers(data); setLoading(false); } })
.catch(e => { if (!cancel) { setError(e); setLoading(false); } });
return () => { cancel = true; };
}, [debouncedQuery]);
return <UserList users={users} loading={loading} error={error} onSearch={setQuery} />;
}
// ...and 4 more list pages that copy-paste this exact block
AFTER — one custom hook, used everywhere:
// hooks/useDebounce.ts
export function useDebounce<T>(value: T, ms = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), ms);
return () => clearTimeout(t);
}, [value, ms]);
return debounced;
}
// hooks/useSearchableList.ts
export function useSearchableList<T>(endpoint: string) {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query);
const { data, isLoading, error } = useQuery({
queryKey: [endpoint, debouncedQuery],
queryFn: () => fetch(`${endpoint}?q=${debouncedQuery}`).then(r => r.json() as Promise<T[]>),
});
return { items: data ?? [], loading: isLoading, error, query, setQuery };
}
function UsersPage() {
const list = useSearchableList<User>('/api/users');
return <UserList {...list} onSearch={list.setQuery} />;
}
function InvoicesPage() {
const list = useSearchableList<Invoice>('/api/invoices');
return <InvoiceList {...list} onSearch={list.setQuery} />;
}
3.4 When to use custom hooks
- Any stateful logic used in 2+ components. Even if it''s small.
- Cross-cutting behavior that needs to plug into component state (online status, geolocation, feature flags, focus management).
- Wrapping browser APIs with a React-friendly interface (
useLocalStorage,useMediaQuery,useIntersectionObserver).
3.5 When NOT to
- Pure, stateless logic — just write a function. A hook that doesn''t call any other hooks is just a function with extra constraints.
- One-off logic used in one component. Inline it until you see the duplication.
3.6 The architect''s rule
If you can name the behavior in a noun ("the searchable list", "the keyboard navigator", "the form draft"), it''s probably a custom hook.
4. Pattern 2 — Container / Presentation
4.1 What it is
Split a component into two: a container that owns data + state, and a presentational component that just renders props. Originally popularized by Dan Abramov in 2015.
4.2 The problem it solves
Components that mix "fetch data" with "render UI" are hard to test (need to mock fetch), hard to reuse (tied to one data source), and hard to swap (server vs client rendering).
4.3 Before / after
BEFORE — fetches and renders in one component:
function UserCard({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
}, [userId]);
if (!user) return <Skeleton />;
return (
<div className="card">
<Avatar src={user.avatar} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
AFTER — separated:
// Presentational — pure, testable, reusable
export function UserCardView({ user }: { user: User }) {
return (
<div className="card">
<Avatar src={user.avatar} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
// Container — owns the data fetch
export function UserCard({ userId }: { userId: string }) {
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
});
return user ? <UserCardView user={user} /> : <Skeleton />;
}
Now UserCardView can be used in Storybook with mock data, rendered server-side with a different data source, and reused in tests without HTTP mocks.
4.4 In 2026: this pattern lives as Server Components + Client Components
// app/users/[id]/page.tsx — Server Component (the new "container")
import { db } from '@/lib/db';
import { UserCardView } from './user-card-view'; // client component
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await db.users.findUnique({ where: { id: params.id } });
return <UserCardView user={user} />;
}
The Server Component is the modern container; the Client Component ('use client') is the modern presentational. The principle survived — the syntax modernized.
4.5 When to use
- Always, as a principle. Whatever owns data should not be the same component that renders it.
- Especially when the same view appears with different data sources (admin vs user, server vs client, real vs mock).
4.6 When NOT to over-split
A trivial component that fetches and renders ten lines doesn''t need two files. Split when the view is genuinely reusable or testable on its own.
5. Pattern 3 — Compound Components
5.1 What it is
A parent component that exposes child components which share state implicitly through context. The API looks like HTML:
<Tabs defaultValue="account">
<Tabs.List>
<Tabs.Trigger value="account">Account</Tabs.Trigger>
<Tabs.Trigger value="billing">Billing</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="account">…</Tabs.Panel>
<Tabs.Panel value="billing">…</Tabs.Panel>
</Tabs>
5.2 The problem it solves
Without compound components, you''d cram everything into props:
// AWFUL: prop-blob API
<Tabs
tabs={[
{ id: 'account', label: 'Account', content: <AccountPanel /> },
{ id: 'billing', label: 'Billing', content: <BillingPanel /> },
]}
defaultValue="account"
/>
This breaks down the moment you need a custom tab style, an icon, a badge, conditional rendering, or anything compositional.
5.3 The implementation
// components/Tabs.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
interface TabsContext {
value: string;
setValue: (v: string) => void;
}
const TabsCtx = createContext<TabsContext | null>(null);
function useTabsCtx() {
const ctx = useContext(TabsCtx);
if (!ctx) throw new Error('Tabs.* must be used inside <Tabs>');
return ctx;
}
export function Tabs({ defaultValue, children }: { defaultValue: string; children: ReactNode }) {
const [value, setValue] = useState(defaultValue);
return <TabsCtx.Provider value={{ value, setValue }}>{children}</TabsCtx.Provider>;
}
Tabs.List = function TabsList({ children }: { children: ReactNode }) {
return <div role="tablist" className="tabs-list">{children}</div>;
};
Tabs.Trigger = function TabsTrigger({ value, children }: { value: string; children: ReactNode }) {
const { value: active, setValue } = useTabsCtx();
return (
<button
role="tab"
aria-selected={active === value}
onClick={() => setValue(value)}
className={active === value ? 'tab tab-active' : 'tab'}
>
{children}
</button>
);
};
Tabs.Panel = function TabsPanel({ value, children }: { value: string; children: ReactNode }) {
const { value: active } = useTabsCtx();
if (active !== value) return null;
return <div role="tabpanel">{children}</div>;
};
Same structure for <Menu>, <Accordion>, <Dialog>, <Combobox> — the entire shadcn/Radix design language is built on this pattern.
5.4 When to use
- Component libraries (your design system).
- Anything that should "look like HTML" — flexible composition matters.
- Form fields, menus, dialogs, tabs, dropdowns, comboboxes, accordions.
5.5 When NOT to
- Single-shot components used in one place.
- When the structure is fixed and unlikely to vary.
5.6 The architect''s rule
If your component will be used by other developers across the team and they''ll want to compose it (insert custom children, change order), it should be a compound component. If it''s a leaf, it shouldn''t.
6. Pattern 4 — Render Props
6.1 What it is
Pass a function as a child (or as a prop) that decides what to render. The parent owns state/behavior; the child decides the UI.
6.2 The classic example
<MouseTracker>
{({ x, y }) => <div>Cursor at {x}, {y}</div>}
</MouseTracker>
6.3 Real production usage
// A form library that owns validation but lets you render anything
<Form initialValues={{ email: '' }} onSubmit={save}>
{({ values, errors, isSubmitting, setField }) => (
<>
<Input value={values.email} onChange={(v) => setField('email', v)} />
{errors.email && <Error>{errors.email}</Error>}
<Button disabled={isSubmitting}>Save</Button>
</>
)}
</Form>
6.4 The 2026 reality
Hooks made render props mostly obsolete. The same Form library today:
function ProfileForm() {
const { values, errors, isSubmitting, setField, handleSubmit } = useForm({
initialValues: { email: '' },
onSubmit: save,
});
return (
<form onSubmit={handleSubmit}>
<Input value={values.email} onChange={(v) => setField('email', v)} />
{errors.email && <Error>{errors.email}</Error>}
<Button disabled={isSubmitting}>Save</Button>
</form>
);
}
Hooks beat render props on:
- Less nesting (no extra component layer in the JSX).
- Easier composition (multiple hooks side-by-side; multiple render props is a pyramid).
- TypeScript ergonomics.
6.5 When render props still wins
Two narrow cases:
- Genuinely "give the parent control over a child''s structure" — e.g., a
<Virtualizer>that needs you to render each item:
<Virtualizer count={10000} estimateSize={() => 44}>
{({ items }) => items.map(i => <Row key={i.key} index={i.index} />)}
</Virtualizer>
- Library APIs where the consumer can''t add a hook to their component tree (rare).
6.6 When NOT to
For 95% of state-sharing problems in 2026: don''t use render props — write a hook.
7. Pattern 5 — Higher-Order Component (HOC)
7.1 What it is
A function that takes a component and returns a new component with additional props or behavior.
const EnhancedComponent = withAuth(MyComponent);
7.2 The classic example
// withAuth.tsx — adds auth checking + injects current user
export function withAuth<P extends { user?: User }>(
Component: React.ComponentType<P>,
) {
return function WithAuth(props: Omit<P, 'user'>) {
const user = useCurrentUser();
if (!user) return <Redirect to="/login" />;
return <Component {...(props as P)} user={user} />;
};
}
// usage
const Dashboard = withAuth(DashboardImpl);
7.3 The 2026 reality — HOCs are the demoted pattern
Hooks made HOCs largely obsolete. The same protection in modern React:
function Dashboard() {
const user = useAuthRequired(); // hook — redirects if not authed
return <DashboardView user={user} />;
}
Hooks beat HOCs on every dimension:
- No wrapper-hell in the React DevTools tree.
- No prop-collision ambiguity (HOCs inject props that can shadow real ones).
- Better TypeScript inference — HOC types are notoriously messy.
- Composition is linear (call multiple hooks) instead of nested wrappers.
- No display-name fiddling for debugging.
7.4 When HOCs still earn their keep
A narrow set:
- Wrapping legacy class components that can''t use hooks.
- Library boundaries where you genuinely need to wrap any component (e.g., React Router''s
withRouterhistorically, error boundaries). - Adding lifecycle behaviors like measuring render time or auto-injecting i18n strings across a whole tree of components from a third-party lib.
If you control the component, prefer a hook. If a new HOC appears in a 2026 PR, ask "could this be a hook?" — the answer is almost always yes.
8. Pattern 6 — Provider Pattern
8.1 What it is
A component that wraps part of the tree and supplies values via Context to descendants — typically combined with a hook that reads the context.
8.2 The canonical implementation
// providers/ThemeProvider.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
type Theme = 'light' | 'dark';
const ThemeCtx = createContext<{ theme: Theme; toggle: () => void } | null>(null);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>('light');
return (
<ThemeCtx.Provider
value={{ theme, toggle: () => setTheme((t) => (t === 'light' ? 'dark' : 'light')) }}
>
<div data-theme={theme}>{children}</div>
</ThemeCtx.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeCtx);
if (!ctx) throw new Error('useTheme must be used inside ThemeProvider');
return ctx;
}
// usage
<ThemeProvider>
<App />
</ThemeProvider>
// anywhere
function Header() {
const { theme, toggle } = useTheme();
return <button onClick={toggle}>Current: {theme}</button>;
}
8.3 When to use
- App-wide concerns that many components consume: theme, auth, locale, feature flags, query client, router.
- Anything provider-shaped in third-party libraries (
<QueryClientProvider>,<I18nProvider>). - Compound components (provider is what makes them work internally).
8.4 When NOT to
- Frequently-changing state with many consumers — every consumer re-renders. Use Zustand/Redux selector for fine-grained updates.
- As a poor man''s state manager. Context isn''t free at scale.
8.5 Composing many providers cleanly
// BEFORE — nesting pyramid
<QueryClientProvider client={qc}>
<ThemeProvider>
<AuthProvider>
<LocaleProvider>
<ToastProvider>
<App />
</ToastProvider>
</LocaleProvider>
</AuthProvider>
</ThemeProvider>
</QueryClientProvider>
// AFTER — compose them
function AppProviders({ children }: { children: ReactNode }) {
return providers.reduce((acc, P) => <P>{acc}</P>, children);
}
const providers = [QueryClientProvider, ThemeProvider, AuthProvider, LocaleProvider, ToastProvider];
9. The patterns in concert — the whole SaaS dashboard
Putting them together in the running example:
┌──────────────────────────────────────────────────────────────────┐
│ <AppProviders> │
│ QueryClientProvider · ThemeProvider · AuthProvider · LocaleProvider │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Route: /settings (Server Component = CONTAINER) │ │
│ │ loads user/team data → passes to <SettingsView/> │ │
│ │ │ │
│ │ <SettingsView> (''use client'', the PRESENTATION) │ │
│ │ <Tabs> ← COMPOUND COMPONENT │ │
│ │ <Tabs.List> │ │
│ │ <Tabs.Trigger value="account">Account</…> │ │
│ │ <Tabs.Panel value="account"> │ │
│ │ <AccountForm /> ← uses useForm() CUSTOM HOOK │ │
│ │ <BillingWizard /> ← internally uses <Form>{...}</> │ │
│ │ (render prop survives) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ </AppProviders> │
└──────────────────────────────────────────────────────────────────┘
Each pattern owns the concern it fits:
- Providers = cross-cutting global state (auth, theme, locale).
- Server / Client component split = data fetching vs rendering.
- Compound
<Tabs>= composable, HTML-shaped API. - Custom hooks (
useForm,useSearchableList) = reusable stateful logic. - Render props inside
<Form>= the one place flexible inversion of control still wins. - HOC = not present. We didn''t need one.
That last point is the architect''s lens: a 2026 app can be coherent without ever writing a new HOC. The pattern is demoted, not retired.
10. Production metrics — measured impact of pattern choices
Real numbers from a SaaS dashboard refactor (95 components, 4 squads, ~6 months of cumulative migration alongside feature work). Same app, before and after consolidating on the patterns above (custom hooks + compound + provider, retiring HOCs and most render props):
| Metric | Before (HOC + Render-Props heavy) | After (Hooks + Compound + Provider) | Δ |
|---|---|---|---|
| Total component code lines | ~28,400 | ~20,500 | −28% |
| Avg component reuse factor (× of times used) | 2.1 | 5.8 | +176% |
| HOCs in the codebase | 14 | 2 (legacy) | −86% |
| Render-prop components | 22 | 4 (<Virtualizer>, <Form>, …) | −82% |
| Custom hooks introduced | 8 | 47 | + |
| Prop-drilling-related bugs (per quarter) | 11 | 4 | −63% |
| Storybook stories that mount without mocks | 38% | 84% | +121% (presentational split) |
| Avg PR review time (frontend) | 1.6 days | 0.7 days | −56% |
| Test runs (frontend CI) | 8m 40s | 3m 50s | −56% (less wrapping = faster mounts) |
| New-dev "first feature shipped" | 9 days | 5 days | −44% |
| DevTools tree depth (median route) | 18 layers | 9 layers | −50% (HOC removal) |
| TypeScript inference errors per week | ~18 | ~5 | −72% (hooks > HOCs for types) |
10.1 Where the wins came from
Where the wins came from:
████████████████████ Custom hooks replacing HOC + RP duplication ~38%
████████████ Server/Client (modern container/presentation) ~22%
█████████ Compound components in the design system ~16%
██████ Providers replacing prop-drilling ~12%
████ Test-without-mocks (presentational components) ~8%
██ Smaller DevTools tree (HOC removal) ~4%
10.2 Cost side — be honest
| Cost | Reality |
|---|---|
| Migration effort | ~6 months, alongside features (not blocking) |
| New convention adoption | Team training session + a patterns.md in the repo |
| Some bikeshedding ("is this a hook or a component?") | A patterns.md ADR resolved most disagreements |
| Initial reuse-factor stayed low for ~2 months | Compounded after a critical mass of shared hooks landed |
Net: the patterns paid back in 4–5 months of normal feature work. The metrics are still improving 18 months later.
11. The decision flow — which pattern for which problem
What kind of concern is this?
│
├── "Logic I want to reuse across components"
│ → CUSTOM HOOK (almost always the answer in 2026)
│
├── "A component that should look like HTML and be composable"
│ → COMPOUND COMPONENT
│
├── "App-wide state that many components need"
│ → PROVIDER (Context + hook). For high-frequency state, prefer Zustand.
│
├── "Separating data from view, mocking for tests, swapping data source"
│ → CONTAINER/PRESENTATION (Server Component + Client Component in modern stacks)
│
├── "Give the parent control over how children render"
│ → Try a CUSTOM HOOK first. Use RENDER PROPS only when the library API
│ truly needs to control the child tree (virtualizers, animation libs).
│
└── "Cross-cutting behavior on a class component or third-party tree"
→ HOC, reluctantly. If hooks are an option, take hooks.
12. Anti-patterns and common mistakes
| Anti-pattern | Why it''s bad | Fix |
|---|---|---|
| Hook abuse — extracting every line into a hook | Indirection without benefit; harder to read | A hook should have a clear noun-named purpose. If you can''t name it, inline it |
| Component over-splitting | 5 files for what should be one | Split when reuse, testing, or server/client split needs it — not for purity |
| God provider | One context with 30 properties; every change re-renders all consumers | Split contexts by change frequency; or move to Zustand for fine-grained updates |
| Render-props pyramids | <A>{a => <B>{b => <C>...}</C>}</B>}</A> | Use hooks; flatten the tree |
| New HOCs in 2026 code | Almost always a hook would be cleaner | Write the hook |
| Compound components for one-off use | Bureaucracy for a leaf component | Plain props are fine for the leaf case |
13. Honest stuff
- Hooks ate the world. Most pattern questions in 2026 have "custom hook" as the answer. Master them first.
- Server Components changed Container/Presentation. The pattern survived; the syntax modernized. If you''re on Next.js App Router, you''re already using it.
- HOCs are not deprecated by the React team — but they are by the community via better alternatives. Don''t add new ones.
- Render props isn''t dead, just niche. Library authors still use it for inversion-of-control over child rendering. Application code rarely needs it.
- The biggest architectural wins are at the level of which pattern goes where, not "should I use Redux." A team with a coherent pattern vocabulary moves 2× faster than one without.
- Patterns aren''t a hierarchy. Each one solves a different shape of problem. The architect picks; the team executes; the codebase stays coherent.
14. The mental checklist when reaching for a pattern
- Have I named the noun of the behavior? ("the searchable list", "the form") → likely a hook.
- Is this composable like HTML? → compound component.
- Is this app-wide and rarely-changing? → provider.
- Is this data vs view? → server/client split (or container/presentation).
- Am I about to write a new HOC? → can I write a hook instead?
- Am I about to write a render prop? → can I write a hook instead?
- Is this used in 2+ places? If yes, extract. If no, inline.
- Will this pattern survive a junior reading it 6 months from now?
15. Closing — the right mental model
React patterns aren''t a checklist of things to use — they''re a vocabulary for shapes of problems. The architect''s job is to know which shape they''re looking at and reach for the matching pattern before someone reinvents it badly.
Three habits that make this work in a team:
- Write the patterns vocabulary down (a
patterns.mdor a short ADR per pattern). The pattern lives in the team''s heads only if it''s also in the repo. - Prefer hooks until forced otherwise. They''ve eaten 80% of what HOCs and render props used to do, for good reasons.
- Compose patterns; don''t choose one. A real app uses all six (or their 2026 equivalents), each where it fits. The architecture is the choices, not the patterns themselves.
Adopt that lens, run the migration playbook above, and the team stops reinventing shapes — every new feature snaps into a place that already has a name.
Further reading
- React docs — Reusing Logic with Custom Hooks — the modern canon.
- Kent C. Dodds — Compound Components with React — the deep dive.
- Dan Abramov — Presentational and Container Components — the original 2015 essay.
- Radix UI primitives and shadcn/ui — production-grade compound-component libraries.
- React docs — Higher-Order Components — the React team''s current take (use hooks instead).
- Patterns.dev — React Patterns — a broader catalogue.
Architecting a new React codebase or refactoring an old one and unsure which pattern fits a specific component? Email randhir.jassal@gmail.com with the shape (what it owns, who uses it, what varies) and I''ll tell you which of the six I''d reach for.
Get the next issue
A short, curated email with the newest posts and questions.