react-development

SKILL.md

React Development

Expert guidance for building high-quality React applications with React 19+, modern hooks, TypeScript, and best practices following official React documentation at https://react.dev.

Skill Paths

  • Workspace skills: .github/skills/
  • Global skills: C:/Users/LOQ/.agents/skills/

Activation Conditions

Core React Development:

  • Building React components with hooks and TypeScript
  • Setting up React applications (Vite, Next.js, CRA)
  • Working with React Router for navigation
  • Implementing forms and user input handling
  • Creating responsive layouts and component architecture

State Management & Data:

  • Managing state (useState, useReducer, useContext)
  • Implementing React Query or SWR for data fetching
  • Building custom hooks for reusable stateful logic
  • Creating Context providers for global state
  • Implementing React 19 Server Components (if using Next.js)

Component Patterns:

  • Creating reusable UI components with proper composition
  • Building forms with React Hook Form and Zod validation
  • Implementing modal dialogs and overlays
  • Creating custom React hooks (useDebounce, useLocalStorage)
  • Designing responsive variants for different screen sizes

Performance & Quality:

  • Optimizing React app performance
  • Implementing memoization (useMemo, useCallback)
  • Code splitting and lazy loading
  • Writing tests with React Testing Library
  • Ensuring accessibility compliance (ARIA, keyboard nav)

Part 1: React Fundamentals

Project Structure

Recommended Structure:

src/
├── components/          # Reusable UI components
│   ├── ui/            # Primitives (Button, Input, Modal)
│   ├── forms/          # Form components
│   └── layout/         # Layout components
├── hooks/              # Custom hooks
├── contexts/            # Context providers
├── pages/              # Page components
├── lib/                # Utilities, API clients
├── types/              # TypeScript types
├── styles/              # Global styles
└── main.jsx            # App entry point

Component Architecture

Functional Components (React 19 Standard)

interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
  disabled?: boolean;
  isLoading?: boolean;
}

export function Button({
  variant = 'primary',
  size = 'md',
  onClick,
  children,
  disabled = false,
  isLoading = false,
}: ButtonProps) {
  const baseClasses = 'rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';

  const variantClasses = {
    primary: 'bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500',
    secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-900 focus:ring-gray-500',
    danger: 'bg-red-600 hover:bg-red-700 text-white focus:ring-red-500',
  };

  const sizeClasses = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-5 py-2.5 text-lg',
  };

  return (
    <button
      disabled={disabled || isLoading}
      onClick={onClick}
      className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
    >
      {isLoading ? 'Loading...' : children}
    </button>
  );
}

Generic Components

interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string;
  emptyMessage?: string;
}

function List<T>({ items, renderItem, keyExtractor, emptyMessage = 'No items' }: ListProps<T>) {
  return (
    <ul>
      {items.length === 0 ? (
        <li>{emptyMessage}</li>
      ) : (
        items.map((item, index) => (
          <li key={keyExtractor(item)}>
            {renderItem(item, index)}
          </li>
        ))
      )}
    </ul>
  );
}

// Usage
<List
  items={users}
  renderItem={(user) => `${user.firstName} ${user.lastName}`}
  keyExtractor={(user) => user.id}
/>

Component Patterns

Composition over Inheritance:

// ✅ GOOD - Component composition
function Card({ children }: { children: React.ReactNode }) {
  return (
    <div className="border rounded-lg p-4 shadow-sm">
      {children}
    </div>
  );
}

function CardHeader({ children }: { children: React.ReactNode }) {
  return (
    <div className="font-bold text-lg mb-2 border-b pb-2">
      {children}
    </div>
  );
}

function CardBody({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>;
}

// Usage
<Card>
  <CardHeader>Title</CardHeader>
  <CardBody>Content here</CardBody>
</Card>

// ❌ BAD - Inheritance or complex props
function Card({ renderHeader, renderBody, renderFooter }: { ... }) { ... }

Presentational vs Container Components:

// Presentational - UI only, doesn't fetch data
interface UserCardProps {
  user: User;
  onStatusChange: (status: string) => void;
}

function UserCard({ user, onStatusChange }: UserCardProps) {
  return (
    <div>
      <h3>{user.name}</h3>
      <select value={user.status} onChange={(e) => onStatusChange(e.target.value)}>
        <option value="active">Active</option>
        <option value="inactive">Inactive</option>
      </select>
    </div>
  );
}

// Container - Fetches data, passes to presentational
function UserContainer() {
  const { data: user, mutate: updateStatus } = useUser(userId);

  return user ? (
    <UserCard user={user} onStatusChange={(status) => updateStatus({ status })} />
  ) : (
    <LoadingSpinner />
  );
}

Part 2: Hooks Patterns

Built-in Hooks Mastery

useState - For Local State

// Simple state
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

// Complex state - use useReducer instead
function Form() {
  type FormState = {
    name: string;
    email: string;
    submitted: boolean;
  };

  const [form, setForm] = useState<FormState>({
    name: '',
    email: '',
    submitted: false,
  });

  const updateField = (field: keyof FormState) => (
    e: React.ChangeEvent<HTMLInputElement>
  ) => setForm(f => ({ ...f, [field]: e.target.value }));

  return (
    <form>
      <input value={form.name} onChange={updateField('name')} />
      <input value={form.email} onChange={updateField('email')} />
    </form>
  );
}

useReducer - For Complex State

type Action =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: number }
  | { type: 'DELETE_TODO'; payload: number };

type TodoState = {
  items: Array<{ id: number; text: string; completed: boolean }>;
};

function todosReducer(state: TodoState, action: Action): TodoState {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        items: [
          ...state.items,
          { id: Date.now(), text: action.payload, completed: false },
        ],
      };
    case 'TOGGLE_TODO':
      return {
        items: state.items.map((item) =>
          item.id === action.payload ? { ...item, completed: !item.completed } : item
        ),
      };
    case 'DELETE_TODO':
      return {
        items: state.items.filter((item) => item.id !== action.payload),
      };
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todosReducer, { items: [] });

  return (
    <div>
      <ToDoList items={state.items} onToggle={(id) => dispatch({ type: 'TOGGLE_TODO', payload: id })} />
    </div>
  );
}

useEffect - For Side Effects

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);

  // ✅ GOOD - Run on mount and when userId changes
  useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      const userData = await api.getUser(userId);
      if (!cancelled) {
        setUser(userData);
      }
    }

    fetchUser();

    return () => {
      cancelled = true; // Cleanup to prevent state updates after unmount
    };
  }, [userId]);

  // ✅ GOOD - Document title update
  useEffect(() => {
    if (user) {
      document.title = `${user.name} - Profile`;
    }
    return () => {
      document.title = 'App';
    };
  }, [user]);

  if (!user) return <LoadingSkeleton />;

  return (
    <div>
      <h1>{user.name}</h1>
      {/* ... */}
    </div>
  );
}

useContext - For Global State

// Context creation
type ThemeContextType = {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
};

const ThemeContext = createContext<ThemeContextType | null>(null);

// Provider component
export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Custom hook for using context
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// Usage in component
function ThemedButton() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button
      onClick={toggleTheme}
      className={theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-black'}
    >
      Toggle {theme} theme
    </button>
  );
}

Performance Hooks

useMemo - Memoize Expensive Computations

function ProductList({ products }: { products: Product[] }) {
  // ✅ GOOD - Memoize expensive calculation
  const sortedProducts = useMemo(() => {
    return [...products].sort((a, b) => a.price - b.price);
  }, [products]);

  const expensiveAnalysis = useMemo(() => {
    // Only recompute when prices change
    const avgPrice = products.reduce((sum, p) => sum + p.price, 0) / products.length;
    const maxPrice = Math.max(...products.map(p => p.price));
    return { avgPrice, maxPrice };
  }, [products]);

  return (
    <div>
      <StatsCard data={expensiveAnalysis} />
      <ProductCards products={sortedProducts} />
    </div>
  );
}

useCallback - Memoize Functions

function ParentComponent() {
  const [items, setItems] = useState<Item[]>([]);

  // ✅ GOOD - Memoize callback to prevent child re-renders
  const handleItemAdd = useCallback((item: Item) => {
    setItems(prev => [...prev, item]);
  }, []);

  const handleItemRemove = useCallback((id: string) => {
    setItems(prev => prev.filter(item => item.id !== id));
  }, []);

  return (
    <div>
      <AddItemForm onAdd={handleItemAdd} />
      <ItemList items={items} onRemove={handleItemRemove} />
    </div>
  );
}

// Child component wrapped in React.memo
const ItemList = React.memo(function ItemList({
  items,
  onRemove,
}: {
  items: Item[];
  onRemove: (id: string) => void;
}) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button onClick={() => onRemove(item.id)}>Remove</button>
        </li>
      ))}
    </ul>
  );
});

Part 3: Custom Hooks

useDebounce

Debounce rapid value changes, useful for search inputs:

import { useState, useEffect } from 'react';

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

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

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

  return debouncedValue;
}

// Usage
function SearchBar() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    // This only runs 300ms after user stops typing
    if (debouncedQuery) {
      performSearch(debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <input
      type="text"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search..."
    />
  );
}

useLocalStorage

Persist state to localStorage:

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 reading localStorage key "${key}":`, 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 setting localStorage key "${key}":`, error);
    }
  };

  return [storedValue, setValue] as const;
}

// Usage
function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light');

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current theme: {theme}
    </button>
  );
}

useToggle

Toggle boolean state with useful utilities:

function useToggle(initialValue: boolean = false) {
  const [value, setValue] = useState(initialValue);

  const toggle = useCallback(() => setValue(v => !v), []);
  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);

  return { value, toggle, setTrue, setFalse, setValue } as const;
}

// Usage
function ModalExample() {
  const { value: isOpen, toggle, setFalse: close } = useToggle(false);

  return (
    <>
      <button onClick={toggle}>Open Modal</button>
      <Modal isOpen={isOpen} onClose={close}>
        <p>Modal content</p>
      </Modal>
    </>
  );
}

useFetch

Data fetching with loading and error states:

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

function useFetch<T>(url: string): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

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

    try {
      const response = await fetch(url);
      const jsonData = await response.json();
      setData(jsonData);
    } catch (err) {
      setError(err as Error);
    } finally {
      setLoading(false);
    }
  }, [url]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

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

// Usage
function UserProfile({ userId }: { userId: string }) {
  const { data: user, loading, error } = useFetch<User>(`/api/users/${userId}`);

  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorAlert message={error.message} />;

  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  );
}

Part 4: Forms & Validation

React Hook Form with Zod

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

// Zod schema for validation
const formSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  age: z.number().min(18, 'Must be at least 18'),
  subscribe: z.boolean().default(false),
});

type FormData = z.infer<typeof formSchema>;

function UserForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<FormData>({
    resolver: zodResolver(formSchema),
  });

  const onSubmit = async (data: FormData) => {
    try {
      await api.createUser(data);
      alert('User created successfully!');
    } catch (error) {
      alert('Error creating user');
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="mb-4">
        <label>Name</label>
        <input {...register('name')} />
        {errors.name && <span className="error">{errors.name.message}</span>}
      </div>

      <div className="mb-4">
        <label>Email</label>
        <input type="email" {...register('email')} />
        {errors.email && <span className="error">{errors.email.message}</span>}
      </div>

      <div className="mb-4">
        <label>Age</label>
        <input type="number" {...register('age', { valueAsNumber: true })} />
        {errors.age && <span className="error">{errors.age.message}</span>}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Creating...' : 'Create User'}
      </button>
    </form>
  );
}

Form Components

interface FormFieldProps {
  label: string;
  error?: string;
  children: React.ReactNode;
}

function FormField({ label, error, children }: FormFieldProps) {
  return (
    <div className="mb-4">
      <label className="block text-sm font-medium mb-1">{label}</label>
      {children}
      {error && <p className="text-red-500 text-sm mt-1">{error}</p>}
    </div>
  );
}

// Usage
<FormField label="Email" error={errors.email?.message}>
  <input
    type="email"
    {...register('email')}
    className="w-full px-3 py-2 border rounded"
  />
</FormField>

Part 5: Tailwind CSS Integration

Utility Classes for React Components

// Button component with variants
const buttonVariants = {
  primary: 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-500',
  secondary: 'bg-gray-200 hover:bg-gray-300 focus:ring-gray-500',
  danger: 'bg-red-600 hover:bg-red-700 focus:ring-red-500',
  ghost: 'bg-transparent hover:bg-gray-100 focus:ring-gray-500',
};

const buttonSizes = {
  sm: 'px-3 py-1.5 text-sm',
  md: 'px-4 py-2 text-base',
  lg: 'px-5 py-2.5 text-lg',
};

export function Button({
  variant = 'primary',
  size = 'md',
  className = '',
  children,
  ...props
}: ButtonProps) {
  return (
    <button
      className={cn(
        'rounded-lg font-medium transition-colors focus:outline-none focus:ring-2',
        buttonVariants[variant],
        buttonSizes[size],
        className
      )}
      {...props}
    >
      {children}
    </button>
  );
}

Responsive Variants

interface CardProps {
  isFeatured?: boolean;
  children: React.ReactNode;
}

export function Card({ isFeatured = false, children }: CardProps) {
  return (
    <div
      className={cn(
        'rounded-lg shadow-md p-6 transition-all',
        'bg-white hover:shadow-lg',
        // Responsive padding
        'sm:p-4 md:p-6 lg:p-8',
        // Featured highlight
        isFeatured && 'ring-2 ring-blue-500 border-2 border-blue-500'
      )}
    >
      {children}
    </div>
  );
}

Part 6: State Management Patterns

Context API for Global State

// AppContext.tsx
interface AppState {
  user: User | null;
  loading: boolean;
  login: (credentials: LoginCredentials) => Promise<void>;
  logout: () => void;
}

const AppContext = createContext<AppState | null>(null);

export function AppProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(false);

  const login = async (credentials: LoginCredentials) => {
    setLoading(true);
    try {
      const userData = await api.login(credentials);
      setUser(userData);
    } finally {
      setLoading(false);
    }
  };

  const logout = () => {
    api.logout();
    setUser(null);
  };

  return (
    <AppContext.Provider value={{ user, loading, login, logout }}>
      {children}
    </AppContext.Provider>
  );
}

export function useApp() {
  const context = useContext(AppContext);
  if (!context) throw new Error('useApp must be used within AppProvider');
  return context;
}

Using React Query (TanStack Query)

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Fetch data
function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => api.getUser(userId),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorAlert message={error.message} />;

  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  );
}

// Mutate data
function UpdateUserForm({ user: initialUser }: { user: User }) {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: api.updateUser,
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['user', initialUser.id] });
    },
  });

  const handleSubmit = (data: Partial<User>) => {
    mutation.mutate({ ...initialUser, ...data });
  };

  return (
    <form onSubmit={(e) => { e.preventDefault(); handleSubmit(/* data */); }}>
      {/* Form fields */}
    </form>
  );
}

Part 7: Performance Optimization

Code Splitting with React.lazy

import { lazy, Suspense } from 'react';

// Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Reports = lazy(() => import('./pages/Reports'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/reports" element={<Reports />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Virtual Scrolling

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50, // Estimated item height
    overscan: 5, // Render extra items beyond viewport
  });

  return (
    <div ref={parentRef} className="h-96 overflow-auto">
      <div
        style={{
          height: `${virtualizer.getTotalSize()}px`,
          position: 'relative',
        }}
      >
        {virtualizer.getVirtualItems().map((virtualItem) => {
          const item = items[virtualItem.index];
          return (
            <div
              key={virtualItem.key}
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                height: `${virtualItem.size}px`,
                transform: `translateY(${virtualItem.start}px)`,
              }}
            >
              {/*
...existing code...
*/}
            </div>
          );
        })}
      </div>
    </div>
  );
}

Part 8: Testing with React Testing Library

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

describe('Button Component', () => {
  it('renders children correctly', () => {
    render(<Button>Click Me</Button>);
    expect(screen.getByText('Click Me')).toBeInTheDocument();
  });

  it('calls onClick handler when clicked', async () => {
    const user = userEvent.setup();
    const handleClick = vi.fn();

    render(<Button onClick={handleClick}>Click Me</Button>);

    await user.click(screen.getByText('Click Me'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('is disabled when disabled prop is true', () => {
    render(<Button disabled>Cannot Click</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

describe('UseFetch Hook', () => {
  it('fetches data and returns it', async () => {
    const { result } = renderHook(() => useFetch('/api/test'));

    await waitFor(() => {
      expect(result.current.data).toBeDefined();
      expect(result.current.loading).toBe(false);
    });
  });
});

React Development Best Practices

Components

  • Use functional components with hooks
  • Keep components small and focused
  • Use TypeScript for type safety
  • Implement proper error boundaries
  • Add loading and error states

Hooks

  • Follow rules of hooks (only call at top level)
  • Use custom hooks for reusable stateful logic
  • Memoize expensive computations and callbacks
  • Clean up side effects properly

Performance

  • Implement code splitting for large bundles
  • Use virtual scrolling for long lists
  • Debounce/throttle user input
  • Lazy load images and resources

Accessibility

  • Use semantic HTML elements
  • Ensure keyboard navigation works
  • Add ARIA labels where needed
  • Support screen readers properly
  • Test with accessibility tools

References & Resources

Documentation

  • Hooks Reference — All 17 React hooks including React 19 new hooks (use, useFormStatus, useOptimistic)
  • Patterns Catalog — 12 React component patterns with TypeScript examples

Scripts

  • Component Generator — PowerShell component scaffolder with types: functional, page, layout, context

Examples


Related Skills

Skill Relationship
frontend-design UI/UX design principles for React apps
vite-development Build tooling for React projects
javascript-development Core JS patterns underlying React
stitch-design Convert Stitch screens to React components
web-testing Test React apps with Playwright
Weekly Installs
5
GitHub Stars
2
First Seen
Feb 26, 2026
Installed on
opencode5
gemini-cli5
claude-code5
github-copilot5
amp5
cline5