Advanced React Patterns in 2026

Knowing useState and useEffect gets a feature shipped. Keeping a codebase pleasant after forty developers and three years have touched it requires patterns that constrain how components can be misused.
Compound Components
A component with twenty props is a component nobody can read the call site of. Compound components move configuration into structure, the way select and option do in HTML.
```jsx
const TabsContext = createContext(null);export function Tabs({ defaultValue, children }) { const [active, setActive] = useState(defaultValue); const value = useMemo(() => ({ active, setActive }), [active]); return <TabsContext.Provider value={value}>{children}</TabsContext.Provider>; }
export function Tab({ value, children }) { const ctx = useContext(TabsContext); if (!ctx) throw new Error('Tab must be rendered inside Tabs'); return ( <button role='tab' aria-selected={ctx.active === value} onClick={() => ctx.setActive(value)} > {children} </button> ); } ```
The thrown error is the pattern working. Misuse fails loudly at development time instead of rendering something subtly broken.
Custom Hooks as the Real Abstraction Boundary
A good custom hook owns one concern completely and exposes a state machine rather than raw booleans. Returning a status string beats returning isLoading, isError, and isSuccess separately, because impossible combinations become unrepresentable.
```js
function useSubmission(submitFn) {
const [state, setState] = useState({ status: 'idle' });
const submit = useCallback(async (input) => {
setState({ status: 'pending' });
try {
const data = await submitFn(input);
setState({ status: 'success', data });
} catch (error) {
setState({ status: 'error', error });
}
}, [submitFn]);
return { ...state, submit };
}Where Render Props Still Win
Hooks replaced render props for sharing logic, not for inverting rendering control. When a component owns behaviour and measurements but the caller must decide the markup, passing a function as a child remains the cleanest option. Virtualised lists, drag-and-drop containers, and chart primitives all still use it because the parent needs to hand down values it computed during layout.
The Server Component Split
The most consequential pattern now is deciding where a component runs. Push data fetching to server components, keep client components small and leaf-shaped, and pass serialisable props across the boundary. The common mistake is marking a top-level layout as a client component for one interactive button, which drags the entire tree into the bundle.
Patterns Worth Retiring
- Higher-order components for logic sharing. Hooks do it with better types and no wrapper hell.
- useEffect for derived state. If a value can be computed during render, compute it during render.
- Global state for server data. A query cache handles staleness, retries, and deduplication far better than a store you maintain.
- Premature memoisation. Measure with the profiler first; unnecessary useMemo adds cost and noise.
The common thread is API design. A component's props are a contract, and good patterns make the wrong call impossible to write rather than merely discouraged in a style guide.
Enjoyed this article?
Share it with your network and join the conversation.