---
description: Modern React + TypeScript component patterns — hooks, typed props, performance optimization, forms, and testing
globs: **/*.tsx,**/*.ts,**/components/**/*.jsx
alwaysApply: false
---
# React TypeScript Component Excellence

## Modern Component Architecture
- **Functional Components**: Use React hooks for state and lifecycle management
- **TypeScript Integration**: Comprehensive type safety with proper interface definitions
- **Performance Optimization**: Implement useMemo, useCallback, and React.memo strategically
- **Accessibility First**: Built-in ARIA support and keyboard navigation

## Component Structure Best Practices
```typescript
import React, { useState, useCallback, useMemo, ReactNode } from 'react'
import { clsx } from 'clsx'

// ✅ Define comprehensive prop interfaces
interface ButtonProps {
  children: ReactNode
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
  size?: 'sm' | 'md' | 'lg'
  disabled?: boolean
  loading?: boolean
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void
  type?: 'button' | 'submit' | 'reset'
  className?: string
  'data-testid'?: string
}

// ✅ Modern functional component with comprehensive TypeScript
export const Button: React.FC<ButtonProps> = ({
  children,
  variant = 'primary',
  size = 'md',
  disabled = false,
  loading = false,
  onClick,
  type = 'button',
  className,
  'data-testid': testId,
  ...rest
}) => {
  // ✅ Memoized class computation for performance
  const buttonClasses = useMemo(() => clsx(
    'btn',
    `btn--${variant}`,
    `btn--${size}`,
    {
      'btn--disabled': disabled,
      'btn--loading': loading,
    },
    className
  ), [variant, size, disabled, loading, className])

  // ✅ Optimized event handler
  const handleClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
    if (disabled || loading) {
      event.preventDefault()
      return
    }
    onClick?.(event)
  }, [disabled, loading, onClick])

  return (
    <button
      type={type}
      className={buttonClasses}
      onClick={handleClick}
      disabled={disabled || loading}
      aria-disabled={disabled || loading}
      aria-busy={loading}
      data-testid={testId}
      {...rest}
    >
      {loading && (
        <span className="btn__spinner" aria-hidden="true">
          <LoadingSpinner size="sm" />
        </span>
      )}
      <span className={loading ? 'btn__content--loading' : 'btn__content'}>
        {children}
      </span>
    </button>
  )
}
```

## Advanced Hook Patterns
```typescript
import { useState, useEffect, useCallback, useRef } from 'react'

// ✅ Custom hook with comprehensive TypeScript
interface UseApiOptions<T> {
  immediate?: boolean
  onSuccess?: (data: T) => void
  onError?: (error: Error) => void
}

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

export function useApi<T>(
  apiCall: () => Promise<T>,
  options: UseApiOptions<T> = {}
): UseApiReturn<T> {
  const { immediate = false, onSuccess, onError } = options
  
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<Error | null>(null)
  const cancelRef = useRef<AbortController | null>(null)

  const execute = useCallback(async () => {
    // Cancel previous request
    if (cancelRef.current) {
      cancelRef.current.abort()
    }

    const controller = new AbortController()
    cancelRef.current = controller

    try {
      setLoading(true)
      setError(null)
      
      const result = await apiCall()
      
      if (!controller.signal.aborted) {
        setData(result)
        onSuccess?.(result)
      }
    } catch (err) {
      if (!controller.signal.aborted) {
        const error = err instanceof Error ? err : new Error('Unknown error')
        setError(error)
        onError?.(error)
      }
    } finally {
      if (!controller.signal.aborted) {
        setLoading(false)
      }
    }
  }, [apiCall, onSuccess, onError])

  const reset = useCallback(() => {
    setData(null)
    setError(null)
    setLoading(false)
    if (cancelRef.current) {
      cancelRef.current.abort()
    }
  }, [])

  useEffect(() => {
    if (immediate) {
      execute()
    }

    return () => {
      if (cancelRef.current) {
        cancelRef.current.abort()
      }
    }
  }, [immediate, execute])

  return { data, loading, error, execute, reset }
}
```

## Form Handling Excellence
```typescript
import { useForm, Controller } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

// ✅ Comprehensive form validation schema
const loginSchema = z.object({
  email: z
    .string()
    .min(1, 'Email is required')
    .email('Please enter a valid email address'),
  password: z
    .string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, 
      'Password must contain at least one uppercase letter, one lowercase letter, and one number'),
  rememberMe: z.boolean().default(false),
})

type LoginFormData = z.infer<typeof loginSchema>

interface LoginFormProps {
  onSubmit: (data: LoginFormData) => Promise<void>
  loading?: boolean
}

export const LoginForm: React.FC<LoginFormProps> = ({ 
  onSubmit, 
  loading = false 
}) => {
  const {
    control,
    handleSubmit,
    formState: { errors, isValid, isSubmitting },
    setError,
    reset,
  } = useForm<LoginFormData>({
    resolver: zodResolver(loginSchema),
    mode: 'onChange',
    defaultValues: {
      email: '',
      password: '',
      rememberMe: false,
    },
  })

  const handleFormSubmit = useCallback(async (data: LoginFormData) => {
    try {
      await onSubmit(data)
      reset()
    } catch (error) {
      // Handle server validation errors
      if (error instanceof ValidationError) {
        error.fieldErrors.forEach(({ field, message }) => {
          setError(field as keyof LoginFormData, { message })
        })
      } else {
        setError('root', { 
          message: 'An unexpected error occurred. Please try again.' 
        })
      }
    }
  }, [onSubmit, reset, setError])

  return (
    <form onSubmit={handleSubmit(handleFormSubmit)} className="login-form">
      <div className="form-group">
        <Controller
          name="email"
          control={control}
          render={({ field, fieldState }) => (
            <Input
              {...field}
              type="email"
              label="Email Address"
              placeholder="Enter your email"
              error={fieldState.error?.message}
              required
              data-testid="email-input"
            />
          )}
        />
      </div>

      <div className="form-group">
        <Controller
          name="password"
          control={control}
          render={({ field, fieldState }) => (
            <Input
              {...field}
              type="password"
              label="Password"
              placeholder="Enter your password"
              error={fieldState.error?.message}
              required
              data-testid="password-input"
            />
          )}
        />
      </div>

      <div className="form-group">
        <Controller
          name="rememberMe"
          control={control}
          render={({ field }) => (
            <Checkbox
              checked={field.value}
              onChange={field.onChange}
              label="Remember me"
              data-testid="remember-me-checkbox"
            />
          )}
        />
      </div>

      {errors.root && (
        <div className="form-error" role="alert">
          {errors.root.message}
        </div>
      )}

      <Button
        type="submit"
        variant="primary"
        size="lg"
        disabled={!isValid || isSubmitting || loading}
        loading={isSubmitting || loading}
        className="w-full"
        data-testid="submit-button"
      >
        {isSubmitting || loading ? 'Signing in...' : 'Sign In'}
      </Button>
    </form>
  )
}
```

## State Management Patterns
```typescript
import { createContext, useContext, useReducer, ReactNode } from 'react'

// ✅ Comprehensive state management with useReducer
interface User {
  id: string
  email: string
  name: string
  role: 'admin' | 'user' | 'guest'
}

interface AuthState {
  user: User | null
  isAuthenticated: boolean
  loading: boolean
  error: string | null
}

type AuthAction =
  | { type: 'AUTH_START' }
  | { type: 'AUTH_SUCCESS'; payload: User }
  | { type: 'AUTH_ERROR'; payload: string }
  | { type: 'LOGOUT' }
  | { type: 'CLEAR_ERROR' }

const initialState: AuthState = {
  user: null,
  isAuthenticated: false,
  loading: false,
  error: null,
}

function authReducer(state: AuthState, action: AuthAction): AuthState {
  switch (action.type) {
    case 'AUTH_START':
      return {
        ...state,
        loading: true,
        error: null,
      }
    case 'AUTH_SUCCESS':
      return {
        ...state,
        user: action.payload,
        isAuthenticated: true,
        loading: false,
        error: null,
      }
    case 'AUTH_ERROR':
      return {
        ...state,
        user: null,
        isAuthenticated: false,
        loading: false,
        error: action.payload,
      }
    case 'LOGOUT':
      return {
        ...state,
        user: null,
        isAuthenticated: false,
        loading: false,
        error: null,
      }
    case 'CLEAR_ERROR':
      return {
        ...state,
        error: null,
      }
    default:
      return state
  }
}

interface AuthContextType extends AuthState {
  login: (email: string, password: string) => Promise<void>
  logout: () => void
  clearError: () => void
}

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

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

interface AuthProviderProps {
  children: ReactNode
}

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, initialState)

  const login = useCallback(async (email: string, password: string) => {
    dispatch({ type: 'AUTH_START' })
    
    try {
      const response = await authApi.login({ email, password })
      dispatch({ type: 'AUTH_SUCCESS', payload: response.user })
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Login failed'
      dispatch({ type: 'AUTH_ERROR', payload: message })
      throw error
    }
  }, [])

  const logout = useCallback(() => {
    authApi.logout()
    dispatch({ type: 'LOGOUT' })
  }, [])

  const clearError = useCallback(() => {
    dispatch({ type: 'CLEAR_ERROR' })
  }, [])

  const value = useMemo(() => ({
    ...state,
    login,
    logout,
    clearError,
  }), [state, login, logout, clearError])

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  )
}
```

## Testing Integration
```typescript
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { vi } from 'vitest'

// ✅ Comprehensive component testing
describe('LoginForm', () => {
  const mockOnSubmit = vi.fn()

  beforeEach(() => {
    mockOnSubmit.mockClear()
  })

  it('should render all form elements correctly', () => {
    render(<LoginForm onSubmit={mockOnSubmit} />)

    expect(screen.getByTestId('email-input')).toBeInTheDocument()
    expect(screen.getByTestId('password-input')).toBeInTheDocument()
    expect(screen.getByTestId('remember-me-checkbox')).toBeInTheDocument()
    expect(screen.getByTestId('submit-button')).toBeInTheDocument()
  })

  it('should validate email format', async () => {
    const user = userEvent.setup()
    render(<LoginForm onSubmit={mockOnSubmit} />)

    const emailInput = screen.getByTestId('email-input')
    await user.type(emailInput, 'invalid-email')
    await user.tab() // Trigger blur event

    await waitFor(() => {
      expect(screen.getByText('Please enter a valid email address')).toBeInTheDocument()
    })
  })

  it('should submit form with valid data', async () => {
    const user = userEvent.setup()
    mockOnSubmit.mockResolvedValueOnce(undefined)

    render(<LoginForm onSubmit={mockOnSubmit} />)

    await user.type(screen.getByTestId('email-input'), 'test@example.com')
    await user.type(screen.getByTestId('password-input'), 'Password123')
    await user.click(screen.getByTestId('submit-button'))

    await waitFor(() => {
      expect(mockOnSubmit).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'Password123',
        rememberMe: false,
      })
    })
  })

  it('should handle server errors gracefully', async () => {
    const user = userEvent.setup()
    mockOnSubmit.mockRejectedValueOnce(new Error('Invalid credentials'))

    render(<LoginForm onSubmit={mockOnSubmit} />)

    await user.type(screen.getByTestId('email-input'), 'test@example.com')
    await user.type(screen.getByTestId('password-input'), 'WrongPassword')
    await user.click(screen.getByTestId('submit-button'))

    await waitFor(() => {
      expect(screen.getByText('An unexpected error occurred. Please try again.')).toBeInTheDocument()
    })
  })
})
```
