react

tenlisboa's avatarfrom tenlisboa

Modern React 18+ patterns with TypeScript, hooks, state management, and performance optimization. This skill should be used when building React components, debugging frontend issues, or reviewing React code. (user)

⭐1stars🔀0forks📁View on GitHub🕐Updated Dec 23, 2025

When & Why to Use This Skill

This Claude skill provides expert guidance on Modern React 18+ development, focusing on TypeScript integration, functional component patterns, and performance optimization. It empowers developers to implement scalable state management, efficient hooks, and robust testing strategies, ensuring frontend applications are built according to 2025 industry best practices.

Use Cases

  • Architecting scalable React applications using modern functional patterns and composition to eliminate prop drilling.
  • Implementing type-safe state management solutions using Zustand or Context API for complex global data flows.
  • Optimizing frontend performance through strategic use of React 18 features like Suspense, lazy loading, and memoization.
  • Developing reusable and generic UI component libraries with TypeScript to ensure strict type safety and maintainability.
  • Conducting professional code reviews to identify anti-patterns, improve hook usage, and enforce clean code standards.
namereact
descriptionModern React 18+ patterns with TypeScript, hooks, state management, and performance optimization. This skill should be used when building React components, debugging frontend issues, or reviewing React code. (user)

React Best Practices (2025)

Component Patterns

Functional Components Only

interface UserCardProps {
  user: User;
  onSelect?: (user: User) => void;
}

function UserCard({ user, onSelect }: UserCardProps) {
  return (
    <article onClick={() => onSelect?.(user)}>
      <h2>{user.name}</h2>
    </article>
  );
}

Props Destructuring

  • Always destructure props in function signature
  • Use default values: { size = 'md' }: Props
  • Spread remaining props: { className, ...rest }

Composition Over Props Drilling

// ❌ Prop drilling
<Parent user={user}>
  <Child user={user}>
    <GrandChild user={user} />
  </Child>
</Parent>

// ✅ Composition
<UserProvider user={user}>
  <Parent>
    <Child>
      <GrandChild />
    </Child>
  </Parent>
</UserProvider>

Page Patterns

Unified Create/Edit Form

Single form component handles both modes via optional entity prop:

interface EntityFormProps {
  entity?: Entity;
  onSuccess: (entity: Entity) => void;
  onCancel: () => void;
}

function EntityForm({ entity, onSuccess, onCancel }: EntityFormProps) {
  const isEdit = Boolean(entity);
  const [formData, setFormData] = useState(entity ?? initialFormData);

  // Conditional mutation based on mode
  // Edit-only features (delete, publish) render when isEdit
}

Pages become thin wrappers:

// CreatePage
<EntityForm onSuccess={(e) => navigate(`/entities/${e.id}`)} onCancel={() => navigate('/entities')} />

// EditPage - fetch data first
const { data: entity } = useEntity(id);
<EntityForm entity={entity} onSuccess={...} onCancel={...} />

Reusable UI Components

Extract repeated patterns: Pagination, Snackbar, DeleteConfirmModal, StatusBadge

Hooks Best Practices

useState

  • Use functional updates for derived state: setCount(c => c + 1)
  • Prefer multiple states over one object
  • Initialize expensive state with function: useState(() => computeExpensive())

useEffect

  • One effect per concern
  • Always include cleanup when needed
  • Avoid objects in dependency arrays (use primitives)
// ✅ Good: Minimal dependencies
useEffect(() => {
  const handler = () => setWidth(window.innerWidth);
  window.addEventListener("resize", handler);
  return () => window.removeEventListener("resize", handler);
}, []); // Empty = mount only

useMemo / useCallback

  • Only for expensive computations
  • Only when passing to memoized children
  • Don't over-optimize prematurely
// Memoize expensive filter
const filtered = useMemo(
  () => items.filter((i) => i.name.includes(search)),
  [items, search],
);

// Memoize callback for React.memo child
const handleClick = useCallback((id: string) => setSelected(id), []);

Custom Hooks

  • Extract reusable logic
  • Name with use prefix
  • Return tuple or object consistently
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;
}

State Management

Hierarchy (prefer top to bottom)

  1. Local state - Component-specific
  2. Lifted state - Shared between siblings
  3. Context - Cross-cutting (theme, auth)
  4. External store - Complex global state (Zustand, Jotai)

When to Use Context

  • Theme/appearance
  • User authentication
  • Locale/i18n
  • Feature flags
const useStore = create<State>((set) => ({
  items: [],
  addItem: (item) => set((s) => ({ items: [...s.items, item] })),
  removeItem: (id) =>
    set((s) => ({
      items: s.items.filter((i) => i.id !== id),
    })),
}));

TypeScript Patterns

Props Types

interface Props {
  required: string;
  optional?: number;
  children: React.ReactNode;
  onClick: (event: React.MouseEvent) => void;
  as?: React.ElementType;
}

Generic Components

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.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>
  );
}

Performance

React.memo

  • Wrap components receiving same props repeatedly
  • Combine with useCallback for function props
  • Don't use everywhere (adds overhead)

Lazy Loading

const HeavyComponent = lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <HeavyComponent />
    </Suspense>
  );
}

Keys

  • Use stable, unique IDs (not array index)
  • Changing key forces remount

Testing (Vitest + Testing Library)

import { render, screen, fireEvent } from "@testing-library/react";

describe("Button", () => {
  it("calls onClick when clicked", () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click</Button>);

    fireEvent.click(screen.getByRole("button"));

    expect(handleClick).toHaveBeenCalledOnce();
  });
});

Common Mistakes

❌ Avoid:

  • Mutating state directly
  • Async operations in render
  • Inline object/array creation in JSX (causes re-renders)
  • Missing keys in lists
  • useEffect without dependencies
  • Over-abstracting too early

✅ Do:

  • Treat state as immutable
  • Use error boundaries
  • Colocate state with usage
  • Memoize expensive computations
  • Use TypeScript strictly