Claude Code Plugins

Community-maintained marketplace

Feedback
0
0

Modern React 19+ patterns with TypeScript

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name react-patterns
description Modern 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