Advanced React Patterns
Implements advanced React patterns for cross-cutting concerns: custom hooks for stateful logic reuse, higher-order components for configuration injection, render props for flexible rendering, portals for out-of-DOM rendering, error boundaries for graceful failure, and ref forwarding for imperative access.
TL;DR Checklist
- Choose hooks over HOCs for stateful logic reuse
- Compose small hooks into larger ones following the single-responsibility principle
- Use createPortal for modals, tooltips, and dropdowns outside parent DOM hierarchy
- Implement error boundaries at route and feature boundaries
- Set displayName on all HOCs for debugging
- Use forwardRef when wrapping native DOM elements
- Test each pattern in isolation before composing
When to Use
Use this skill when:
- Extracting reusable stateful logic shared across multiple components
- Building modals, tooltips, or dropdowns that need to escape overflow/scroll containers
- Adding error catch-all boundaries to feature sections or routes
- Wrapping third-party DOM elements that need ref access through component layers
- Implementing cross-cutting concerns (logging, analytics, authentication) across many components
- Creating flexible component APIs where the consumer controls rendering
- Exposing imperative methods (scrollTo, focus, reset) on a component via refs
When NOT to Use
Avoid this skill for:
- Simple state management — useState and useEffect suffice
- One-off UI patterns that don't need reuse — premature abstraction
- Async data fetching without shared logic — use
react-client-data-fetchinginstead - Components that never expose refs or need portal rendering
- Situations where custom hooks already solve the problem — don't add HOC complexity
Core Workflow
Identify the Cross-Cutting Concern — Is it state logic, configuration injection, flexible rendering, or DOM escape? Each maps to a different pattern.
Select the Appropriate Pattern:
- Stateful logic → custom hooks
- Configuration/behavior injection → HOC
- Flexible render control → render props
- Escape parent container → portals (createPortal)
- Catch rendering errors → error boundaries
- Expose imperative methods → forwardRef + useImperativeHandle
Implement with TypeScript — Use proper generic types for maximum reusability.
Test in Isolation — Verify each pattern works independently before composing multiple patterns together.
Implementation Patterns
Pattern 1: Custom Hooks Composition
// ✅ GOOD: Small, composable hooks — each has one responsibility
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initialValue;
} catch {
return initialValue;
}
});
const setValue = useCallback(
(value: T | ((prev: T) => T)) => {
setStoredValue((prev) => {
const valueToStore = value instanceof Function ? value(prev) : value;
window.localStorage.setItem(key, JSON.stringify(valueToStore));
return valueToStore;
});
},
[key]
);
return [storedValue, setValue] as const;
}
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(() => matchMedia(query).matches);
useEffect(() => {
const mql = matchMedia(query);
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
mql.addEventListener('change', handler);
return () => mql.removeEventListener('change', handler);
}, [query]);
return matches;
}
// Compose them for a complex behavior
function useResponsiveLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useLocalStorage(key, initialValue);
const isMobile = useMediaQuery('(max-width: 768px)');
const adjustedValue = useMemo(
() => (isMobile ? truncateForMobile(value) : value),
[value, isMobile]
);
return [adjustedValue, setValue] as const;
}
Pattern 2: Error Boundary with Granular Fallback (BAD vs. GOOD)
// ❌ BAD: Class component error boundary — works but verbose and lacks TypeScript generics
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) return <h1>Something went wrong</h1>;
return this.props.children;
}
}
// ✅ GOOD: Error boundary with typed fallback component and granular control
interface ErrorBoundaryProps {
children: React.ReactNode;
fallback?: React.ComponentType<{ error: Error; reset: () => void }>;
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
this.props.onError?.(error, errorInfo);
}
handleReset = () => {
this.setState({ hasError: false, error: null });
};
render(): React.ReactNode {
if (this.state.hasError && this.state.error) {
const Fallback = this.props.fallback;
if (Fallback) {
return <Fallback error={this.state.error} reset={this.handleReset} />;
}
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Usage: wrap routes or feature sections
<ErrorBoundary
fallback={({ error, reset }) => (
<div role="alert">
<h2>Section failed to load</h2>
<pre>{error.message}</pre>
<button
</div>
)}
=> logErrorToService(err)}
>
<UserProfile userId={id} />
</ErrorBoundary>
Pattern 3: Portal for Modal Rendering
// ✅ GOOD: Portal-based modal escapes parent overflow/stacking context
import { createPortal } from 'react-dom';
interface PortalProps {
children: React.ReactNode;
container?: Element | DocumentFragment;
}
function Portal({ children, container = document.body }: PortalProps) {
return createPortal(children, container);
}
function Modal({ isOpen, onClose, children }: ModalProps) {
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
if (isOpen) {
document.addEventListener('keydown', handleEscape);
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = '';
};
}, [isOpen, onClose]);
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay"
<div className="modal-content" => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}
Pattern 4: forwardRef + useImperativeHandle
// ✅ GOOD: Expose imperative methods through forwarded ref
interface FancyInputHandle {
focus: () => void;
reset: () => void;
select: () => void;
}
interface FancyInputProps {
defaultValue?: string;
onValueChange?: (value: string) => void;
}
const FancyInput = forwardRef<FancyInputHandle, FancyInputProps>(
function FancyInput({ defaultValue, onValueChange }, ref) {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(
ref,
() => ({
focus: () => inputRef.current?.focus(),
reset: () => {
if (inputRef.current) {
inputRef.current.value = defaultValue ?? '';
onValueChange?.(defaultValue ?? '');
}
},
select: () => inputRef.current?.select(),
}),
[defaultValue, onValueChange]
);
return (
<input
ref={inputRef}
defaultValue={defaultValue}
=> onValueChange?.(e.target.value)}
/>
);
}
);
// Parent uses imperative handle
function Form() {
const inputRef = useRef<FancyInputHandle>(null);
return (
<>
<FancyInput ref={inputRef} defaultValue="hello" />
<button => inputRef.current?.reset()}>Reset</button>
</>
);
}
Constraints
MUST DO
- Use custom hooks over HOCs for stateful logic reuse
- Set
displayNameon every HOC for debugging (DevTools) - Use
createPortalfor modals, dialogs, tooltips, and dropdowns - Implement error boundaries at route, feature, or widget boundaries
- Use
forwardRefwhen wrapping native DOM elements - Expose imperative methods via
useImperativeHandlewhen a component needs focus/reset/scroll - Clean up portal DOM nodes and event listeners on unmount
MUST NOT DO
- Over-abstract simple patterns — choose the simplest pattern that works
- Use render props or HOCs when custom hooks suffice
- Forget to reset error boundary state before retrying
- Nest portals unnecessarily — one level of portal is usually sufficient
- Ignore the ref type parameter on forwardRef — always type the handle interface
- Forget displayName on HOCs — debugging becomes impossible in React DevTools
Related Skills
| Skill | Purpose |
|---|---|
react-composition-patterns |
Component composition patterns for building flexible UIs |
react-rerender-optimization |
Memoization strategies to avoid unnecessary re-renders |
react-client-data-fetching |
Data fetching patterns with Suspense and error boundaries |
react-async-waterfalls |
Avoiding request waterfalls in component hierarchies |
Live References
Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.