react-patterns

majiayu000's avatarfrom majiayu000

Modern React 19+ patterns with TypeScript

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 5, 2026

When & Why to Use This Skill

This Claude skill provides a comprehensive framework for developing modern React 19+ applications using TypeScript. It enforces industry-leading best practices, including strict type safety, functional component patterns, and robust error handling. By prioritizing named exports, discriminated unions for state management, and the elimination of deprecated patterns like React.FC, it ensures the generation of maintainable, high-performance, and production-ready frontend code.

Use Cases

  • Developing scalable React 19 applications with strict TypeScript type safety and modern component architectures.
  • Refactoring legacy React codebases to align with React 19 standards and modern TypeScript conventions.
  • Implementing type-safe state management using discriminated unions to eliminate illegal component states and improve reliability.
  • Standardizing team coding styles by enforcing named exports, readonly props, and explicit error handling patterns.
  • Optimizing component performance and maintainability by avoiding deprecated features like defaultProps and React.FC.
namereact-patterns
descriptionModern React 19+ patterns with TypeScript

React Patterns

Purpose: Modern React 19+ with TypeScript (type safety, error handling, component best practices)

  • Keywords: component, react, hook, jsx, tsx, useState, useEffect, useCallback, useMemo, ReactNode, form, FormEvent, input, validation, props, children, ref, render, conditional

Quick Reference

Pattern ✅ Modern (React 19+) ❌ Avoid
Components Named exports Default exports
Props readonly always Mutable props
Types No React.FC React.FC<Props>
Props Explicit typing defaultProps
State Discriminated unions Bag of optionals
Error handling Result types Unchecked try/catch

Component Structure

import type { ReactNode } from "react"

interface ButtonProps {
  readonly variant: "primary" | "secondary"
  readonly onClick: () => void
  readonly disabled?: boolean          // Safe defaults only
  readonly userId: string | undefined  // Critical: explicit
  readonly children?: ReactNode
}

export function Button({ variant, onClick, disabled, children }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={variant === "primary" ? "btn-primary" : "btn-secondary"}
    >
      {children}
    </button>
  )
}

Key:

  • Named export (not default)
  • No React.FC (outdated in React 19)
  • Props interface with readonly
  • Explicit undefined for critical fields
  • Optional ? only for safe defaults

State with Discriminated Unions

import { useState, useEffect } from "react"

type AsyncState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: Error }

export function UserProfile({ userId }: { readonly userId: string }) {
  const [state, setState] = useState<AsyncState<User>>({ status: "idle" })

  useEffect(() => {
    setState({ status: "loading" })
    fetchUser(userId).then(
      data => setState({ status: "success", data }),
      error => setState({ status: "error", error })
    )
  }, [userId])

  switch (state.status) {
    case "idle": return null
    case "loading": return <Spinner />
    case "success": return <UserCard user={state.data} />
    case "error": return <ErrorMessage error={state.error} />
  }
}

Event Handlers

export function OrderForm() {
  const handleSubmit = (e: FormEvent) => {
    e.preventDefault()
    // ...
  }

  const handleAmountChange = (value: string) => { /* ... */ }

  return <form onSubmit={handleSubmit}>...</form>
}

Convention: handle* prefix

Props Readonly Always

interface OrderFormProps {
  readonly initialAmount: string
  readonly onSubmit: (order: Order) => void
  readonly onCancel?: () => void
}

export function OrderForm({ initialAmount, onSubmit }: OrderFormProps) {
  return <form>...</form>
}

Result Types

type Result<T, E extends Error> =
  | { ok: true; value: T }
  | { ok: false; error: E }

const parseJson = (input: string): Result<unknown, Error> => {
  try {
    return { ok: true, value: JSON.parse(input) }
  } catch (e) {
    return { ok: false, error: e as Error }
  }
}

// Usage
const result = parseJson(userInput)
if (!result.ok) return <Error message={result.error.message} />
// TypeScript knows result.value is safe

When to throw vs Result:

  • ✅ Throw: Framework handlers, validation, critical failures
  • ✅ Result: Parsing (JSON/dates), file ops, network

Check Latest Docs First

Identify framework:

  • next.config.js = Next.js
  • routeTree.gen.ts = TanStack Start
  • convex/ = Convex

Consult:

JSDoc When Helpful

/**
 * Formats bitcoin amount with proper decimals
 * @link {parseBitcoinAmount} for parsing
 */
export function formatBitcoinAmount(sats: number): string {
  return (sats / 100_000_000).toFixed(8)
}

Guidelines: Only when behavior isn't self-evident, use @link for internal refs

Avoid Outdated Patterns

// ❌ React.FC (discouraged React 19)
const Button: React.FC<Props> = ({ children }) => { }

// ❌ defaultProps (deprecated React 19)
Button.defaultProps = { variant: "primary" }

// ✅ ES6 defaults
export function Button({ variant = "primary" }: Props) { }

// ❌ Default exports (except framework requirements)
export default function MyComponent() { }

// ❌ Mutable props
interface Props { count: number }  // Should be readonly

Resources

  • resources/react-19-2-features.md - useEffectEvent, Activity, cacheSignal
  • resources/hooks-best-practices.md - useEffect, useCallback, useMemo
  • resources/error-boundaries.md - Error boundary patterns
  • resources/performance.md - React.memo, useMemo, useCallback