# React Advanced Patterns

> Implements advanced React patterns including render props, higher-order components, custom hooks composition, portals, error boundaries, and ref forwarding for complex component architectures.

- Skill: `paulpas/react-advanced-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paulpas/react-advanced-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulpas/react-advanced-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: MIT
- Author: paulpas (https://skillmd.com/u/paulpas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulpas/react-advanced-patterns

---


# 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-fetching` instead
- Components that never expose refs or need portal rendering
- Situations where custom hooks already solve the problem — don't add HOC complexity

---

## Core Workflow

1. **Identify the Cross-Cutting Concern** — Is it state logic, configuration injection, flexible rendering, or DOM escape? Each maps to a different pattern.

2. **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

3. **Implement with TypeScript** — Use proper generic types for maximum reusability.

4. **Test in Isolation** — Verify each pattern works independently before composing multiple patterns together.

---

## Implementation Patterns

### Pattern 1: Custom Hooks Composition

```typescript
// ✅ 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)

```typescript
// ❌ 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;
  }
}
```

```typescript
// ✅ 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 onClick={reset}>Retry</button>
    </div>
  )}
  onError={(err) => logErrorToService(err)}
>
  <UserProfile userId={id} />
</ErrorBoundary>
```

### Pattern 3: Portal for Modal Rendering

```typescript
// ✅ 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" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.body
  );
}
```

### Pattern 4: forwardRef + useImperativeHandle

```typescript
// ✅ 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}
        onChange={(e) => onValueChange?.(e.target.value)}
      />
    );
  }
);

// Parent uses imperative handle
function Form() {
  const inputRef = useRef<FancyInputHandle>(null);

  return (
    <>
      <FancyInput ref={inputRef} defaultValue="hello" />
      <button onClick={() => inputRef.current?.reset()}>Reset</button>
    </>
  );
}
```

---

## Constraints

### MUST DO
- Use custom hooks over HOCs for stateful logic reuse
- Set `displayName` on every HOC for debugging (DevTools)
- Use `createPortal` for modals, dialogs, tooltips, and dropdowns
- Implement error boundaries at route, feature, or widget boundaries
- Use `forwardRef` when wrapping native DOM elements
- Expose imperative methods via `useImperativeHandle` when 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.

- [React.createPortal API](https://react.dev/reference/react-dom/createPortal)
- [React Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)
- [React forwardRef API](https://react.dev/reference/react/forwardRef)
- [React useImperativeHandle](https://react.dev/reference/react/useImperativeHandle)
- [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks)
- [Higher-Order Components (Legacy)](https://react.dev/legacy/higher-order-components)
- [Render Props Pattern (Legacy)](https://react.dev/legacy/render-props)

