Claude Code Plugins

Community-maintained marketplace

Feedback

Expert in React for building modern web applications. Use when working with React, React hooks, state management (Redux, Zustand, Context), component architecture, performance optimization, or when the user mentions React, JSX, functional components, hooks, or frontend development.

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 Expert
description Expert in React for building modern web applications. Use when working with React, React hooks, state management (Redux, Zustand, Context), component architecture, performance optimization, or when the user mentions React, JSX, functional components, hooks, or frontend development.

React Expert

A specialized skill for building production-ready React applications with modern patterns, hooks, performance optimization, and best practices.

Instructions

Core Workflow

  1. Understand requirements

    • Identify component needs and hierarchy
    • Determine state management requirements (local, Context, Redux, Zustand)
    • Understand performance requirements
    • Identify routing needs (React Router)
  2. Project structure

    • Organize components logically
    • Separate concerns (components, hooks, utils, types)
    • Configure TypeScript for type safety
    • Set up testing infrastructure
  3. Implement features

    • Create reusable components
    • Implement custom hooks
    • Manage state appropriately
    • Optimize performance (memo, useMemo, useCallback)
    • Handle side effects properly
  4. Testing and optimization

    • Write tests with React Testing Library
    • Optimize bundle size
    • Implement code splitting
    • Profile and optimize performance

React Project Structure

src/
├── components/
│   ├── common/              # Reusable components
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.test.tsx
│   │   │   └── Button.module.css
│   │   └── Input/
│   ├── features/            # Feature-specific components
│   │   ├── auth/
│   │   └── dashboard/
│   └── layout/              # Layout components
│       ├── Header.tsx
│       └── Footer.tsx
├── hooks/                   # Custom hooks
│   ├── useAuth.ts
│   ├── useApi.ts
│   └── useLocalStorage.ts
├── contexts/                # Context providers
│   └── AuthContext.tsx
├── store/                   # State management (Redux/Zustand)
│   ├── slices/
│   └── store.ts
├── services/                # API services
│   └── api.ts
├── utils/                   # Utility functions
│   └── format.ts
├── types/                   # TypeScript types
│   └── index.ts
├── pages/                   # Page components (if using routing)
│   ├── Home.tsx
│   └── Dashboard.tsx
└── App.tsx

Modern Component Patterns

// Functional component with TypeScript
import { FC, useState, useEffect, useMemo, useCallback } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserListProps {
  initialUsers?: User[];
  onUserSelect?: (user: User) => void;
}

export const UserList: FC<UserListProps> = ({ 
  initialUsers = [],
  onUserSelect 
}) => {
  const [users, setUsers] = useState<User[]>(initialUsers);
  const [searchTerm, setSearchTerm] = useState('');
  const [loading, setLoading] = useState(false);

  // Fetch users on mount
  useEffect(() => {
    const fetchUsers = async () => {
      setLoading(true);
      try {
        const response = await fetch('/api/users');
        const data = await response.json();
        setUsers(data);
      } catch (error) {
        console.error('Failed to fetch users:', error);
      } finally {
        setLoading(false);
      }
    };

    if (initialUsers.length === 0) {
      fetchUsers();
    }
  }, [initialUsers.length]);

  // Memoized filtered users
  const filteredUsers = useMemo(() => {
    return users.filter(user =>
      user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
      user.email.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }, [users, searchTerm]);

  // Memoized callback
  const handleUserClick = useCallback((user: User) => {
    onUserSelect?.(user);
  }, [onUserSelect]);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <input
        type="text"
        placeholder="Search users..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <ul>
        {filteredUsers.map(user => (
          <li key={user.id} onClick={() => handleUserClick(user)}>
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
};

Custom Hooks

// useApi.ts - Generic API hook
import { useState, useEffect } from 'react';

interface UseApiOptions<T> {
  url: string;
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  body?: any;
  dependencies?: any[];
}

interface UseApiReturn<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => Promise<void>;
}

export function useApi<T = any>({
  url,
  method = 'GET',
  body,
  dependencies = []
}: UseApiOptions<T>): UseApiReturn<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = async () => {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch(url, {
        method,
        headers: {
          'Content-Type': 'application/json',
        },
        body: body ? JSON.stringify(body) : undefined,
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err as Error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, dependencies);

  return { data, loading, error, refetch: fetchData };
}

// useLocalStorage.ts
import { useState, useEffect } from 'react';

export 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 (error) {
      console.error(error);
      return initialValue;
    }
  });

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

  return [storedValue, setValue] as const;
}

// useDebounce.ts
import { useState, useEffect } from 'react';

export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

Context API for State Management

// AuthContext.tsx
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

interface AuthContextType {
  user: User | null;
  loading: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Check for existing session
    const checkAuth = async () => {
      try {
        const response = await fetch('/api/auth/me');
        if (response.ok) {
          const userData = await response.json();
          setUser(userData);
        }
      } catch (error) {
        console.error('Auth check failed:', error);
      } finally {
        setLoading(false);
      }
    };

    checkAuth();
  }, []);

  const login = async (email: string, password: string) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });

    if (!response.ok) {
      throw new Error('Login failed');
    }

    const userData = await response.json();
    setUser(userData);
  };

  const logout = () => {
    fetch('/api/auth/logout', { method: 'POST' });
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{
      user,
      loading,
      login,
      logout,
      isAuthenticated: !!user,
    }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

Performance Optimization

import { memo, useMemo, useCallback } from 'react';

// Memoized component (only re-renders if props change)
export const ExpensiveComponent = memo(({ data }: { data: any[] }) => {
  return (
    <div>
      {data.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
});

// Parent component
export const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [users, setUsers] = useState([]);

  // Memoize expensive calculations
  const sortedUsers = useMemo(() => {
    return [...users].sort((a, b) => a.name.localeCompare(b.name));
  }, [users]);

  // Memoize callbacks to prevent child re-renders
  const handleUserAdd = useCallback((user) => {
    setUsers(prev => [...prev, user]);
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {/* ExpensiveComponent won't re-render when count changes */}
      <ExpensiveComponent data={sortedUsers} />
    </div>
  );
};

Form Handling with React Hook Form

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';

const schema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ["confirmPassword"],
});

type FormData = z.infer<typeof schema>;

export const RegisterForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data: FormData) => {
    try {
      await fetch('/api/register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
    } catch (error) {
      console.error('Registration failed:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input {...register('email')} placeholder="Email" />
        {errors.email && <span>{errors.email.message}</span>}
      </div>

      <div>
        <input {...register('password')} type="password" placeholder="Password" />
        {errors.password && <span>{errors.password.message}</span>}
      </div>

      <div>
        <input {...register('confirmPassword')} type="password" placeholder="Confirm Password" />
        {errors.confirmPassword && <span>{errors.confirmPassword.message}</span>}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Register'}
      </button>
    </form>
  );
};

Testing with React Testing Library

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserList } from './UserList';

describe('UserList', () => {
  const mockUsers = [
    { id: 1, name: 'John Doe', email: 'john@example.com' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
  ];

  it('renders users', () => {
    render(<UserList initialUsers={mockUsers} />);

    expect(screen.getByText('John Doe - john@example.com')).toBeInTheDocument();
    expect(screen.getByText('Jane Smith - jane@example.com')).toBeInTheDocument();
  });

  it('filters users by search term', async () => {
    render(<UserList initialUsers={mockUsers} />);

    const searchInput = screen.getByPlaceholderText('Search users...');
    await userEvent.type(searchInput, 'John');

    expect(screen.getByText('John Doe - john@example.com')).toBeInTheDocument();
    expect(screen.queryByText('Jane Smith - jane@example.com')).not.toBeInTheDocument();
  });

  it('calls onUserSelect when user is clicked', async () => {
    const onUserSelect = jest.fn();
    render(<UserList initialUsers={mockUsers} onUserSelect={onUserSelect} />);

    const firstUser = screen.getByText('John Doe - john@example.com');
    fireEvent.click(firstUser);

    expect(onUserSelect).toHaveBeenCalledWith(mockUsers[0]);
  });

  it('shows loading state', () => {
    render(<UserList />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });
});

Critical Rules

Always Do

  • Use TypeScript for type safety
  • Implement proper error boundaries
  • Memoize expensive calculations with useMemo
  • Memoize callbacks with useCallback when passing to child components
  • Use React.memo for components that render often with same props
  • Keep components small and focused
  • Extract reusable logic into custom hooks
  • Write tests for components
  • Use proper key props in lists
  • Handle loading and error states

Never Do

  • Never mutate state directly
  • Never use index as key in dynamic lists
  • Never forget cleanup in useEffect
  • Never create functions inside JSX (causes re-renders)
  • Never ignore ESLint warnings
  • Never use inline styles for everything (use CSS modules or styled-components)
  • Never skip error boundaries
  • Never ignore accessibility (use semantic HTML, ARIA labels)

Knowledge Base

  • React Core: Hooks, Context, Suspense, Error Boundaries
  • State Management: Redux, Zustand, Jotai, Context API
  • Routing: React Router, TanStack Router
  • Forms: React Hook Form, Formik
  • Testing: React Testing Library, Jest
  • Styling: CSS Modules, Styled Components, Tailwind CSS
  • Performance: Code splitting, lazy loading, memoization
  • TypeScript: Type safety, generics, utility types

Integration with Other Skills

  • Works with: Fullstack Guardian, Playwright Expert, Test Master
  • Complements: React Native Expert (mobile), Code Reviewer

Best Practices Summary

  1. TypeScript: Use for all components and hooks
  2. Hooks: Prefer functional components with hooks
  3. Custom Hooks: Extract reusable logic
  4. Performance: Memo, useMemo, useCallback where needed
  5. Testing: Comprehensive tests with React Testing Library
  6. Accessibility: Semantic HTML, ARIA labels, keyboard navigation
  7. Code Splitting: Lazy load routes and heavy components
  8. Error Handling: Error boundaries for graceful degradation
  9. State Management: Choose appropriate solution for complexity
  10. Clean Code: Small, focused, well-named components