mcpField
R
react-patterns

react-patterns

github.com/dralgorhythmDevelopment12964 installsUpdated 15w ago
Repository

About react-patterns

A skill for maintainable React applications using TypeScript, composition patterns, and React 19 features. It provides implementation patterns and checks for web and mobile React development.

Key features

  1. 01Component composition patterns
  2. 02Typed props and events
  3. 03Reusable custom hooks
  4. 04React 19 hooks and features
  5. 05State and performance patterns
  6. 06Suspense and error boundaries

Use cases

  1. 01Designing reusable component APIs
  2. 02Sharing logic across components
  3. 03Managing complex UI state
  4. 04Checking loading and error behavior

MCP support

The README recommends Context7 MCP to look up the full React API reference.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/dralgorhythm/claude-agentic-framework --skill react-patterns

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

Details

From the project README

React Patterns

Platform: Web and Mobile (shared React patterns). For React Native-specific patterns (Pressable, ScrollView, FlashList, safe areas), see the react-native-patterns skill.

Overview

Patterns for building maintainable React applications with TypeScript, leveraging React 19 features and composition patterns.

API Reference: Use Context7 MCP for full React API reference (mcp__context7__resolve-library-id → react).

Workflows

  • Choose appropriate component composition pattern
  • Apply TypeScript types for props and events
  • Implement custom hooks for shared logic
  • Add performance optimizations where needed
  • Handle loading and error states with Suspense/boundaries
  • Validate component render behavior

Feedback Loops

  • Components render without TypeScript errors
  • Props are properly typed and validated
  • Custom hooks have clear return types
  • No unnecessary re-renders (use React DevTools Profiler)
  • Error boundaries catch component errors
  • Loading states work with Suspense

Reference Implementation

1. Component Composition
Compound Components
// Shares implicit state between parent and children
const TabsContext = createContext<{ activeTab: string; setActiveTab: (id: string) => void } | null>(null);

function Tabs({ children, defaultTab }: { children: ReactNode; defaultTab: string }) {
  const [activeTab, setActiveTab] = useState(defaultTab);
  return <TabsContext.Provider value={{ activeTab, setActiveTab }}>{children}</TabsContext.Provider>;
}

function Tab({ id, children }: { id: string; children: ReactNode }) {
  const ctx = use(TabsContext);
  if (!ctx) throw new Error('Tab must be used within Tabs');
  return (
    <button role="tab" aria-selected={ctx.activeTab === id} onClick={() => ctx.setActiveTab(id)}>
      {children}
    </button>
  );
}
Slot Pattern
// Named slots for flexible composition
interface CardProps { header?: ReactNode; footer?: ReactNode; children: ReactNode; }

function Card({ header, footer, children }: CardProps) {
  return (
    <div className="card">
      {header && <div className="card-header">{header}</div>}
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}
2. React 19 Features
use() Hook
// Unwrap promises and context — simpler than useContext
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise); // Suspends until resolved
  return <div>{user.name}</div>;
}
useActionState
async function updateUser(prevState: { error?: string }, formData: FormData) {
  'use server';
  return { error: undefined };
}

function UserForm() {
  const [state, formAction, isPending] = useActionState(updateUser, {});
  return (
    <form action={formAction}>
      <input name="name" disabled={isPending} />
      {state.error && <p className="error">{state.error}</p>}
      <button disabled={isPending}>{isPending ? 'Saving...' : 'Save'}</button>
    </form>
  );
}
useOptimistic
function TodoList({ todos }: { todos: Todo[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo: Todo) => [...state, newTodo]
  );
  async function handleAdd(formData: FormData) {
    const todo = { id: crypto.randomUUID(), text: formData.get('text') as string };
    addOptimisticTodo(todo);
    await saveTodo(todo);
  }
  return (
    <form action={handleAdd}>
      {optimisticTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}
      <input name="text" /><button>Add</button>
    </form>
  );
}
3. Custom Hooks
// Object return for multiple named values
function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const login = async (credentials: Credentials) => { setUser(await api.login(credentials)); };
  return { user, loading, login };
}

// Tuple return for positional access (like useState)
function useToggle(initial = false): [boolean, () => void] {
  const [value, setValue] = useState(initial);
  return [value, useCallback(() => setValue(v => !v), [])];
}

// Composing hooks
function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initial;
  });
  useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]);
  return [value, setValue] as const;
}
4. TypeScript + React
// Extend HTML element props
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary';
  loading?: boolean;
}
function Button({ variant = 'primary', loading, children, ...props }: ButtonProps) {
  return <button className={variant} disabled={loading} {...props}>{loading ? 'Loading...' : children}</button>;
}

// Generic components with full type inference
interface ListProps<T> { items: T[]; renderItem: (item: T) => ReactNode; keyExtractor: (item: T) => string; }
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return <ul>{items.map(item => <li key={keyExtractor(item)}>{renderItem(item)}</li>)}</ul>;
}

// Refs as props (React 19+) — forwardRef is deprecated
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string;
  ref?: React.Ref<HTMLInputElement>;
}
function Input({ label, ref, ...props }: InputProps) {
  return <label>{label}<input ref={ref} {...props} /></label>;
}
5. State Management
useReducer for Complex State
type Action =
  | { type: 'FETCH_START' }
  | { type: 'FETCH_SUCCESS'; payload: User[] }
  | { type: 'FETCH_ERROR'; payload: string };

function reducer(state: { data: User[]; loading: boolean; error: string | null }, action: Action) {
  switch (action.type) {
    case 'FETCH_START': return { ...state, loading: true, error: null };
    case 'FETCH_SUCCESS': return { ...state, loading: false, data: action.payload };
    case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload };
  }
}
6. Performance Patterns
// memo + useCallback prevent unnecessary re-renders
const ListItem = memo(function ListItem({ item, onDelete }: ItemProps) {
  return <li>{item.name}<button onClick={() => onDelete(item.id)}>Delete</button></li>;
});

function List() {
  const [items, setItems] = useState<Item[]>([]);
  const handleDelete = useCallback((id: string) => {
    setItems(items => items.filter(item => item.id !== id));
  }, []);
  return <>{items.map(item => <ListItem key={item.id} item={item} onDelete={handleDelete} />)}</>;
}

// Lazy loading with Suspense
const Dashboard = lazy(() => import('./Dashboard'));
function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes><Route path="/dashboard" element={<Dashboard />} /></Routes>
    </Suspense>
  );
}
7. Error Handling
class ErrorBoundary extends Component<{ children: ReactNode; fallback?: ReactNode }, { hasError: boolean }> {
  state = { hasError: false };
  static getDerivedStateFromError(): { hasError: boolean } { return { hasError: true }; }
  componentDidCatch(error: Error, info: ErrorInfo) { console.error(error, info); }
  render() {
    return this.state.hasError ? (this.props.fallback ?? <div>Something went wrong</div>) : this.props.children;
  }
}

// Wrap async data loading with Suspense
function UserProfile({ userId }: { userId: string }) {
  const user = use(fetchUser(userId)); // Suspends
  return <div>{user.name}</div>;
}

Best Practices

  • Composition over inheritance - Use composition patterns for flexibility
  • Type everything - Leverage TypeScript for compile-time safety
  • Colocate state - Keep state as close to where it's used as possible
  • Extract custom hooks - Share logic across components with hooks
  • Stable references - Use useCallback/useMemo to prevent unnecessary re-renders
  • Error boundaries - Wrap component trees to catch rendering errors
  • Suspense for loading - Use Suspense instead of manual loading states
  • Avoid prop drilling - Use context or composition for deeply nested props

Anti-Patterns

  • Using forwardRef in React 19 - Use ref as a regular prop instead
  • Class components for new code - Use function components and hooks
  • Mutating state directly - Always use setState or reducer actions
  • Missing dependency arrays - Include all dependencies in useEffect/useMemo/useCallback
  • Overusing useMemo/useCallback - Only optimize when necessary (profile first)
  • Context for everything - Use context sparingly; prefer props or state management library
  • Derived state in useState - Compute derived values during render instead
  • useEffect for derived state - Use useMemo or compute directly in render
  • Index as key - Use stable unique IDs for list keys
  • Ignoring TypeScript errors - Never use 'any' or '// @ts-ignore' as shortcuts

Comments

Markdown supported