mcpField
R
react-patterns

react-patterns

github.com/claude-dev-suiteDevelopment4641 installsUpdated 4d ago
Repository

About react-patterns

A component-pattern skill for React developers, covering compound components, render props, HOCs, custom hooks, and composition, with guidance on when to use them and common pitfalls.

Key features

  1. 01Compound component pattern
  2. 02Render props and HOCs
  3. 03Custom hooks
  4. 04Component composition practices
  5. 05Pattern selection and troubleshooting

Use cases

  1. 01Build flexible related components
  2. 02Reuse stateful logic
  3. 03Separate component logic from UI
  4. 04Troubleshoot context or rendering issues

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill react-patterns

After installing, type/react-patternsin Claude Code to confirm it loaded.

Bundled filesadvanced.md

Details

From the project README

React Patterns

Full Reference: See advanced.md for Container/Presentational, State Machine, Slots, Polymorphic, and Provider patterns.

Compound Components

Components that work together to form a cohesive UI:

import { createContext, useContext, useState, ReactNode } from 'react';

interface AccordionContextValue {
  openItems: Set<string>;
  toggle: (id: string) => void;
}

const AccordionContext = createContext<AccordionContextValue | null>(null);

function useAccordion() {
  const context = useContext(AccordionContext);
  if (!context) {
    throw new Error('Accordion components must be used within Accordion');
  }
  return context;
}

function Accordion({ children, multiple = false, defaultOpen = [] }: AccordionProps) {
  const [openItems, setOpenItems] = useState<Set<string>>(new Set(defaultOpen));

  const toggle = (id: string) => {
    setOpenItems((prev) => {
      const next = new Set(prev);
      if (next.has(id)) {
        next.delete(id);
      } else {
        if (!multiple) next.clear();
        next.add(id);
      }
      return next;
    });
  };

  return (
    <AccordionContext.Provider value={{ openItems, toggle }}>
      <div className="accordion">{children}</div>
    </AccordionContext.Provider>
  );
}

// Attach sub-components
Accordion.Item = Item;
Accordion.Trigger = Trigger;
Accordion.Content = Content;

Render Props

Pass a function as children or prop to control rendering:

interface DataFetcherProps<T> {
  url: string;
  children: (state: {
    data: T | null;
    loading: boolean;
    error: Error | null;
    refetch: () => void;
  }) => ReactNode;
}

function DataFetcher<T>({ url, children }: DataFetcherProps<T>) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error('Failed to fetch');
      setData(await res.json());
    } catch (e) {
      setError(e as Error);
    } finally {
      setLoading(false);
    }
  }, [url]);

  useEffect(() => { fetchData(); }, [fetchData]);

  return <>{children({ data, loading, error, refetch: fetchData })}</>;
}

// Usage
<DataFetcher<User[]> url="/api/users">
  {({ data, loading, error, refetch }) => {
    if (loading) return <Spinner />;
    if (error) return <ErrorMessage error={error} onRetry={refetch} />;
    return <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
  }}
</DataFetcher>

Higher-Order Components (HOC)

Wrap components to add functionality:

function withLoading<P extends object>(
  WrappedComponent: React.ComponentType<P>,
  LoadingComponent: React.ComponentType = DefaultSpinner
) {
  return function WithLoadingComponent(props: P & { loading: boolean }) {
    const { loading, ...restProps } = props;
    if (loading) return <LoadingComponent />;
    return <WrappedComponent {...(restProps as P)} />;
  };
}

// HOC with Refs
function withLogger<P extends object>(WrappedComponent: React.ComponentType<P>) {
  const WithLogger = React.forwardRef<unknown, P>((props, ref) => {
    useEffect(() => { console.log('Props changed:', props); }, [props]);
    return <WrappedComponent {...props} ref={ref} />;
  });
  WithLogger.displayName = `WithLogger(${WrappedComponent.displayName || WrappedComponent.name})`;
  return WithLogger;
}

Custom Hooks Pattern

Extract reusable logic into hooks:

// useLocalStorage
function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch { return initialValue; }
  });

  const setValue = useCallback((value: T | ((val: T) => T)) => {
    const valueToStore = value instanceof Function ? value(storedValue) : value;
    setStoredValue(valueToStore);
    window.localStorage.setItem(key, JSON.stringify(valueToStore));
  }, [key, storedValue]);

  return [storedValue, setValue] as const;
}

// useDebounce
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debouncedValue;
}

// useOnClickOutside
function useOnClickOutside(
  ref: RefObject<HTMLElement>,
  handler: (event: MouseEvent | TouchEvent) => void
) {
  useEffect(() => {
    const listener = (event: MouseEvent | TouchEvent) => {
      if (!ref.current || ref.current.contains(event.target as Node)) return;
      handler(event);
    };
    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);
    return () => {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
  }, [ref, handler]);
}

Composition Pattern

Prefer composition over inheritance:

function Card({ children, className }: CardProps) {
  return <div className={`card ${className || ''}`}>{children}</div>;
}

function CardHeader({ children }: { children: ReactNode }) {
  return <div className="card-header">{children}</div>;
}

function CardBody({ children }: { children: ReactNode }) {
  return <div className="card-body">{children}</div>;
}

// Specialized cards through composition
function UserCard({ user }: { user: User }) {
  return (
    <Card>
      <CardHeader>
        <Avatar src={user.avatar} />
        <h3>{user.name}</h3>
      </CardHeader>
      <CardBody><p>{user.bio}</p></CardBody>
    </Card>
  );
}

Best Practices Summary

PatternWhen to Use
Compound ComponentsFlexible, related components
Render PropsShare behavior, control rendering
HOCCross-cutting concerns, legacy code
Custom HooksReusable stateful logic
CompositionBuilding complex from simple
Container/PresentationalSeparate logic from UI
State MachineComplex state transitions
PolymorphicFlexible element types
  • Use custom hooks for reusable logic
  • Keep components small and focused
  • Use TypeScript for better DX
  • Don't overuse patterns - KISS
  • Don't nest too many HOCs

Anti-Patterns

Anti-PatternProblemSolution
Over-engineering simple componentsUnnecessary complexityUse simple props
Nesting too many HOCsProps collision, hard to debugUse composition or hooks
Render props without memoizationPerformance issuesMemoize render function
Deep prop drillingHard to maintainUse Context or Compound Components
Context for frequently changing valuesPerformance issuesUse state library

Quick Troubleshooting

IssueLikely CauseFix
Context not availableMissing ProviderWrap with Provider component
HOC losing refsNot forwarding refsUse React.forwardRef
Render props re-renderingFunction identity changesMemoize render function

Reference Documentation

Comments

Markdown supported