Best Practices & Patterns

Best practices for side effects, API type safety, styling, i18n/a11y, and code-quality tooling.

What are your preferred patterns for handling side effects in React?

Answer:

1. useEffect Hook Patterns:

// Basic data fetching pattern
const useUserData = (userId) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    
    const fetchUser = async () => {
      try {
        setLoading(true);
        setError(null);
        const userData = await api.getUser(userId);
        
        if (!cancelled) {
          setUser(userData);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };

    fetchUser();
    
    return () => {
      cancelled = true; // Cleanup to prevent state updates on unmounted component
    };
  }, [userId]);

  return { user, loading, error };
};

2. Custom Hooks for Reusable Side Effects:

// Debounced search hook
const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);

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

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

  return debouncedValue;
};

// Usage
const SearchComponent = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 300);
  const { results, loading } = useSearchResults(debouncedSearchTerm);

  return (
    <div>
      <input 
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Search..."
      />
      {loading && <div>Searching...</div>}
      {results.map(result => <div key={result.id}>{result.title}</div>)}
    </div>
  );
};

3. Event Listeners and Cleanup:

const useWindowSize = () => {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    window.addEventListener('resize', handleResize);
    
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return windowSize;
};

4. Subscription Management:

const useWebSocket = (url) => {
  const [socket, setSocket] = useState(null);
  const [lastMessage, setLastMessage] = useState(null);
  const [connectionStatus, setConnectionStatus] = useState('disconnected');

  useEffect(() => {
    const ws = new WebSocket(url);
    
    ws.onopen = () => {
      setConnectionStatus('connected');
      setSocket(ws);
    };
    
    ws.onmessage = (event) => {
      setLastMessage(JSON.parse(event.data));
    };
    
    ws.onclose = () => {
      setConnectionStatus('disconnected');
      setSocket(null);
    };
    
    ws.onerror = (error) => {
      console.error('WebSocket error:', error);
      setConnectionStatus('error');
    };

    return () => {
      ws.close();
    };
  }, [url]);

  const sendMessage = useCallback((message) => {
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify(message));
    }
  }, [socket]);

  return { lastMessage, connectionStatus, sendMessage };
};

5. Advanced Patterns with useReducer:

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return {
        ...state,
        loading: true,
        error: null,
      };
    case 'FETCH_SUCCESS':
      return {
        ...state,
        loading: false,
        data: action.payload,
        error: null,
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        loading: false,
        error: action.payload,
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
};

const useDataFetcher = (initialUrl) => {
  const [url, setUrl] = useState(initialUrl);
  const [state, dispatch] = useReducer(dataFetchReducer, {
    data: null,
    loading: false,
    error: null,
  });

  useEffect(() => {
    let cancelled = false;

    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await fetch(url);
        const data = await result.json();
        
        if (!cancelled) {
          dispatch({ type: 'FETCH_SUCCESS', payload: data });
        }
      } catch (error) {
        if (!cancelled) {
          dispatch({ type: 'FETCH_FAILURE', payload: error.message });
        }
      }
    };

    if (url) {
      fetchData();
    }

    return () => {
      cancelled = true;
    };
  }, [url]);

  return { ...state, setUrl };
};

6. Best Practices:

  • Always provide cleanup functions to prevent memory leaks
  • Use dependency arrays correctly to avoid infinite loops
  • Extract complex side effects into custom hooks for reusability
  • Handle loading and error states consistently
  • Use useCallback and useMemo to prevent unnecessary re-renders
  • Consider using libraries like React Query for complex data fetching scenarios

How do you ensure type safety when working with external APIs?

Answer:

1. API Response Type Definitions:

// Define API response types
interface User {
  id: number;
  name: string;
  email: string;
  avatar?: string;
  createdAt: string;
  updatedAt: string;
}

interface ApiResponse<T> {
  data: T;
  message: string;
  status: 'success' | 'error';
  timestamp: string;
}

interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  };
  message: string;
  status: 'success' | 'error';
}

// Specific API response types
type UserResponse = ApiResponse<User>;
type UsersListResponse = PaginatedResponse<User>;

2. API Client with Type Safety:

class ApiClient {
  private baseURL: string;
  private defaultHeaders: Record<string, string>;

  constructor(baseURL: string) {
    this.baseURL = baseURL;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
    };
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;
    const config: RequestInit = {
      ...options,
      headers: {
        ...this.defaultHeaders,
        ...options.headers,
      },
    };

    try {
      const response = await fetch(url, config);
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      return data as T;
    } catch (error) {
      console.error('API request failed:', error);
      throw error;
    }
  }

  // Typed API methods
  async getUsers(page: number = 1, limit: number = 10): Promise<UsersListResponse> {
    return this.request<UsersListResponse>(`/users?page=${page}&limit=${limit}`);
  }

  async getUser(id: number): Promise<UserResponse> {
    return this.request<UserResponse>(`/users/${id}`);
  }

  async createUser(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<UserResponse> {
    return this.request<UserResponse>('/users', {
      method: 'POST',
      body: JSON.stringify(userData),
    });
  }

  async updateUser(id: number, userData: Partial<User>): Promise<UserResponse> {
    return this.request<UserResponse>(`/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(userData),
    });
  }
}

3. Runtime Validation with Zod:

import { z } from 'zod';

// Define schemas for runtime validation
const UserSchema = z.object({
  id: z.number(),
  name: z.string().min(1),
  email: z.string().email(),
  avatar: z.string().url().optional(),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

const ApiResponseSchema = z.object({
  data: z.any(),
  message: z.string(),
  status: z.enum(['success', 'error']),
  timestamp: z.string(),
});

// Type-safe API client with validation
class ValidatedApiClient extends ApiClient {
  async getUsers(page: number = 1, limit: number = 10): Promise<UsersListResponse> {
    const response = await this.request(`/users?page=${page}&limit=${limit}`);
    
    // Runtime validation
    const validatedResponse = ApiResponseSchema.parse(response);
    
    // Validate the data array
    if (Array.isArray(validatedResponse.data)) {
      validatedResponse.data.forEach(user => UserSchema.parse(user));
    }
    
    return validatedResponse as UsersListResponse;
  }

  async getUser(id: number): Promise<UserResponse> {
    const response = await this.request(`/users/${id}`);
    
    // Validate the response structure
    const validatedResponse = ApiResponseSchema.parse(response);
    
    // Validate the user data
    const validatedUser = UserSchema.parse(validatedResponse.data);
    
    return {
      ...validatedResponse,
      data: validatedUser,
    } as UserResponse;
  }
}

4. React Hooks with Type Safety:

// Custom hook for API calls
const useApi = <T>(apiCall: () => Promise<T>) => {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;

    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        const result = await apiCall();
        
        if (!cancelled) {
          setData(result);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err instanceof Error ? err.message : 'An error occurred');
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };

    fetchData();

    return () => {
      cancelled = true;
    };
  }, [apiCall]);

  return { data, loading, error };
};

// Usage with type safety
const UserProfile: React.FC<{ userId: number }> = ({ userId }) => {
  const { data: userResponse, loading, error } = useApi<UserResponse>(
    () => apiClient.getUser(userId)
  );

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!userResponse?.data) return <div>User not found</div>;

  const user = userResponse.data; // TypeScript knows this is a User

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      {user.avatar && <img src={user.avatar} alt={user.name} />}
    </div>
  );
};

5. Error Handling with Discriminated Unions:

// Define error types
interface ApiError {
  type: 'API_ERROR';
  message: string;
  statusCode: number;
  details?: Record<string, any>;
}

interface ValidationError {
  type: 'VALIDATION_ERROR';
  message: string;
  field: string;
  value: any;
}

interface NetworkError {
  type: 'NETWORK_ERROR';
  message: string;
  originalError: Error;
}

type ApiResult<T> = 
  | { success: true; data: T }
  | { success: false; error: ApiError | ValidationError | NetworkError };

// Type-safe error handling
const safeApiCall = async <T>(
  apiCall: () => Promise<T>
): Promise<ApiResult<T>> => {
  try {
    const data = await apiCall();
    return { success: true, data };
  } catch (error) {
    if (error instanceof TypeError && error.message.includes('fetch')) {
      return {
        success: false,
        error: {
          type: 'NETWORK_ERROR',
          message: 'Network request failed',
          originalError: error,
        },
      };
    }

    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: {
          type: 'VALIDATION_ERROR',
          message: 'Data validation failed',
          field: error.errors[0]?.path.join('.') || 'unknown',
          value: error.errors[0]?.received,
        },
      };
    }

    return {
      success: false,
      error: {
        type: 'API_ERROR',
        message: error instanceof Error ? error.message : 'Unknown error',
        statusCode: 500,
      },
    };
  }
};

6. Best Practices:

  • Always define interfaces for API responses
  • Use runtime validation with libraries like Zod or Yup
  • Implement proper error handling with discriminated unions
  • Create type-safe API clients with generic methods
  • Use custom hooks to encapsulate API logic
  • Validate data at runtime to catch API changes
  • Use TypeScript strict mode for better type checking
  • Document API contracts and keep types in sync

What's your approach to styling in React applications? (CSS Modules, Styled Components, Tailwind, etc.)

Answer:

1. Tailwind CSS (Preferred Approach):

// Component with Tailwind classes
const Button: React.FC<ButtonProps> = ({ 
  variant = 'primary', 
  size = 'md', 
  children, 
  ...props 
}) => {
  const baseClasses = 'font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';
  
  const variantClasses = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
    secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
    danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
  };
  
  const sizeClasses = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg',
  };

  return (
    <button
      className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
      {...props}
    >
      {children}
    </button>
  );
};

// Responsive design with Tailwind
const Dashboard: React.FC = () => {
  return (
    <div className="min-h-screen bg-gray-50">
      <header className="bg-white shadow-sm border-b border-gray-200">
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div className="flex justify-between items-center h-16">
            <h1 className="text-xl font-semibold text-gray-900">Dashboard</h1>
            <nav className="hidden md:flex space-x-8">
              <a href="#" className="text-gray-500 hover:text-gray-900">Home</a>
              <a href="#" className="text-gray-500 hover:text-gray-900">Analytics</a>
            </nav>
          </div>
        </div>
      </header>
      
      <main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
          <div className="bg-white overflow-hidden shadow rounded-lg">
            <div className="p-5">
              <div className="flex items-center">
                <div className="flex-shrink-0">
                  <div className="w-8 h-8 bg-blue-500 rounded-md flex items-center justify-center">
                    <span className="text-white text-sm font-medium">📊</span>
                  </div>
                </div>
                <div className="ml-5 w-0 flex-1">
                  <dl>
                    <dt className="text-sm font-medium text-gray-500 truncate">
                      Total Revenue
                    </dt>
                    <dd className="text-lg font-medium text-gray-900">
                      $45,231.89
                    </dd>
                  </dl>
                </div>
              </div>
            </div>
          </div>
        </div>
      </main>
    </div>
  );
};

2. CSS Modules:

// Button.module.css
.button {
  @apply font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2;
}

.primary {
  @apply bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500;
}

.secondary {
  @apply bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500;
}

.small {
  @apply px-3 py-1.5 text-sm;
}

.medium {
  @apply px-4 py-2 text-base;
}

.large {
  @apply px-6 py-3 text-lg;
}

// Button.tsx
import styles from './Button.module.css';
import { clsx } from 'clsx';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary';
  size?: 'small' | 'medium' | 'large';
}

const Button: React.FC<ButtonProps> = ({ 
  variant = 'primary', 
  size = 'medium', 
  className,
  children, 
  ...props 
}) => {
  return (
    <button
      className={clsx(
        styles.button,
        styles[variant],
        styles[size],
        className
      )}
      {...props}
    >
      {children}
    </button>
  );
};

3. Styled Components:

import styled, { css } from 'styled-components';

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  fullWidth?: boolean;
}

const StyledButton = styled.button<ButtonProps>`
  font-weight: 500;
  border-radius: 0.5rem;
  transition: all 0.2s ease-in-out;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  
  &:focus {
    outline: none;
    box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
  }
  
  ${({ variant = 'primary' }) => {
    switch (variant) {
      case 'primary':
        return css`
          background-color: #2563eb;
          color: white;
          &:hover {
            background-color: #1d4ed8;
          }
        `;
      case 'secondary':
        return css`
          background-color: #e5e7eb;
          color: #111827;
          &:hover {
            background-color: #d1d5db;
          }
        `;
      case 'danger':
        return css`
          background-color: #dc2626;
          color: white;
          &:hover {
            background-color: #b91c1c;
          }
        `;
    }
  }}
  
  ${({ size = 'md' }) => {
    switch (size) {
      case 'sm':
        return css`
          padding: 0.375rem 0.75rem;
          font-size: 0.875rem;
        `;
      case 'md':
        return css`
          padding: 0.5rem 1rem;
          font-size: 1rem;
        `;
      case 'lg':
        return css`
          padding: 0.75rem 1.5rem;
          font-size: 1.125rem;
        `;
    }
  }}
  
  ${({ fullWidth }) =>
    fullWidth &&
    css`
      width: 100%;
    `}
`;

// Theme provider setup
const theme = {
  colors: {
    primary: '#2563eb',
    secondary: '#6b7280',
    success: '#10b981',
    danger: '#dc2626',
  },
  spacing: {
    xs: '0.25rem',
    sm: '0.5rem',
    md: '1rem',
    lg: '1.5rem',
    xl: '2rem',
  },
  breakpoints: {
    mobile: '768px',
    tablet: '1024px',
    desktop: '1280px',
  },
};

const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  return <StyledThemeProvider theme={theme}>{children}</StyledThemeProvider>;
};

4. Emotion (CSS-in-JS Alternative):

import { css } from '@emotion/react';
import styled from '@emotion/styled';

// Using css prop
const Card: React.FC = () => {
  return (
    <div
      css={css`
        background: white;
        border-radius: 8px;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
        padding: 1.5rem;
        margin-bottom: 1rem;
        
        &:hover {
          box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
          transform: translateY(-2px);
          transition: all 0.2s ease-in-out;
        }
      `}
    >
      <h3
        css={css`
          margin: 0 0 1rem 0;
          color: #1f2937;
          font-size: 1.25rem;
          font-weight: 600;
        `}
      >
        Card Title
      </h3>
      <p
        css={css`
          margin: 0;
          color: #6b7280;
          line-height: 1.5;
        `}
      >
        Card content goes here...
      </p>
    </div>
  );
};

// Using styled components with Emotion
const StyledInput = styled.input`
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: 0.375rem;
  font-size: 1rem;
  
  &:focus {
    outline: none;
    border-color: #2563eb;
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
  }
  
  &::placeholder {
    color: #9ca3af;
  }
`;

5. Design System Approach:

// Design tokens
export const tokens = {
  colors: {
    primary: {
      50: '#eff6ff',
      500: '#3b82f6',
      900: '#1e3a8a',
    },
    gray: {
      50: '#f9fafb',
      500: '#6b7280',
      900: '#111827',
    },
  },
  spacing: {
    xs: '0.25rem',
    sm: '0.5rem',
    md: '1rem',
    lg: '1.5rem',
    xl: '2rem',
  },
  typography: {
    fontFamily: {
      sans: ['Inter', 'system-ui', 'sans-serif'],
      mono: ['JetBrains Mono', 'monospace'],
    },
    fontSize: {
      sm: '0.875rem',
      base: '1rem',
      lg: '1.125rem',
      xl: '1.25rem',
    },
  },
  breakpoints: {
    sm: '640px',
    md: '768px',
    lg: '1024px',
    xl: '1280px',
  },
};

// Utility functions
export const createResponsiveValue = <T>(values: Partial<Record<keyof typeof tokens.breakpoints, T>>) => {
  return Object.entries(values)
    .map(([breakpoint, value]) => {
      const minWidth = tokens.breakpoints[breakpoint as keyof typeof tokens.breakpoints];
      return `@media (min-width: ${minWidth}) { ${value} }`;
    })
    .join(' ');
};

6. Best Practices & Recommendations:

When to use each approach:

  • Tailwind CSS: Best for rapid prototyping, consistent design systems, and utility-first development
  • CSS Modules: Good for component-scoped styles with traditional CSS syntax
  • Styled Components: Ideal for dynamic styling based on props and complex component logic
  • Emotion: Great alternative to Styled Components with better performance
  • CSS-in-JS: Best for applications requiring runtime theming and dynamic styles

General Guidelines:

  1. Consistency: Choose one primary approach and stick to it
  2. Performance: Consider bundle size and runtime performance
  3. Developer Experience: Ensure good TypeScript support and tooling
  4. Maintainability: Use design tokens and consistent naming conventions
  5. Accessibility: Ensure styles support keyboard navigation and screen readers
  6. Responsive Design: Always consider mobile-first approach
  7. Theme Support: Plan for dark mode and theme switching if needed

How do you handle internationalization (i18n) and accessibility (a11y) in React apps?

Answer:

1. Internationalization (i18n) with react-i18next:

// i18n configuration
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

// Translation resources
const resources = {
  en: {
    translation: {
      welcome: 'Welcome',
      hello: 'Hello {{name}}!',
      buttons: {
        save: 'Save',
        cancel: 'Cancel',
        delete: 'Delete',
      },
      messages: {
        success: 'Operation completed successfully',
        error: 'An error occurred',
        confirm: 'Are you sure you want to delete this item?',
      },
      navigation: {
        home: 'Home',
        about: 'About',
        contact: 'Contact',
      },
    },
  },
  es: {
    translation: {
      welcome: 'Bienvenido',
      hello: '¡Hola {{name}}!',
      buttons: {
        save: 'Guardar',
        cancel: 'Cancelar',
        delete: 'Eliminar',
      },
      messages: {
        success: 'Operación completada exitosamente',
        error: 'Ocurrió un error',
        confirm: '¿Estás seguro de que quieres eliminar este elemento?',
      },
      navigation: {
        home: 'Inicio',
        about: 'Acerca de',
        contact: 'Contacto',
      },
    },
  },
};

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources,
    fallbackLng: 'en',
    debug: process.env.NODE_ENV === 'development',
    
    interpolation: {
      escapeValue: false, // React already escapes values
    },
    
    detection: {
      order: ['localStorage', 'navigator', 'htmlTag'],
      caches: ['localStorage'],
    },
  });

export default i18n;

2. Using i18n in Components:

import { useTranslation } from 'react-i18next';

// Basic usage
const WelcomePage: React.FC = () => {
  const { t, i18n } = useTranslation();

  const changeLanguage = (lng: string) => {
    i18n.changeLanguage(lng);
  };

  return (
    <div>
      <h1>{t('welcome')}</h1>
      <p>{t('hello', { name: 'John' })}</p>
      
      <div>
        <button onClick={() => changeLanguage('en')}>English</button>
        <button onClick={() => changeLanguage('es')}>Español</button>
      </div>
    </div>
  );
};

// Advanced usage with namespaces
const UserProfile: React.FC = () => {
  const { t } = useTranslation(['user', 'common']);
  
  return (
    <div>
      <h2>{t('user:profile.title')}</h2>
      <p>{t('user:profile.description')}</p>
      <button>{t('common:buttons.save')}</button>
    </div>
  );
};

// Custom hook for complex translations
const useLocalizedDate = (date: Date) => {
  const { i18n } = useTranslation();
  
  return useMemo(() => {
    return new Intl.DateTimeFormat(i18n.language, {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
    }).format(date);
  }, [date, i18n.language]);
};

3. Accessibility (a11y) Implementation:

// Accessible form component
const AccessibleForm: React.FC = () => {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
  });
  const [errors, setErrors] = useState<Record<string, string>>({});

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    // Form validation logic
  };

  return (
    <form onSubmit={handleSubmit} noValidate>
      <fieldset>
        <legend>Contact Information</legend>
        
        <div className="form-group">
          <label htmlFor="name" className="required">
            Full Name
            <span className="sr-only"> (required)</span>
          </label>
          <input
            id="name"
            type="text"
            value={formData.name}
            onChange={(e) => setFormData({ ...formData, name: e.target.value })}
            aria-describedby={errors.name ? 'name-error' : undefined}
            aria-invalid={!!errors.name}
            required
          />
          {errors.name && (
            <div id="name-error" role="alert" className="error-message">
              {errors.name}
            </div>
          )}
        </div>

        <div className="form-group">
          <label htmlFor="email" className="required">
            Email Address
            <span className="sr-only"> (required)</span>
          </label>
          <input
            id="email"
            type="email"
            value={formData.email}
            onChange={(e) => setFormData({ ...formData, email: e.target.value })}
            aria-describedby={errors.email ? 'email-error' : undefined}
            aria-invalid={!!errors.email}
            required
          />
          {errors.email && (
            <div id="email-error" role="alert" className="error-message">
              {errors.email}
            </div>
          )}
        </div>

        <div className="form-group">
          <label htmlFor="message">Message</label>
          <textarea
            id="message"
            value={formData.message}
            onChange={(e) => setFormData({ ...formData, message: e.target.value })}
            rows={4}
            aria-describedby="message-help"
          />
          <div id="message-help" className="help-text">
            Please provide details about your inquiry.
          </div>
        </div>

        <button type="submit" className="primary-button">
          Send Message
        </button>
      </fieldset>
    </form>
  );
};

4. Accessible Navigation:

const AccessibleNavigation: React.FC = () => {
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const menuRef = useRef<HTMLUListElement>(null);
  const buttonRef = useRef<HTMLButtonElement>(null);

  const toggleMenu = () => {
    setIsMenuOpen(!isMenuOpen);
  };

  const closeMenu = () => {
    setIsMenuOpen(false);
    buttonRef.current?.focus();
  };

  // Handle keyboard navigation
  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Escape') {
      closeMenu();
    }
  };

  // Handle focus management
  useEffect(() => {
    if (isMenuOpen && menuRef.current) {
      const firstMenuItem = menuRef.current.querySelector('a');
      firstMenuItem?.focus();
    }
  }, [isMenuOpen]);

  return (
    <nav role="navigation" aria-label="Main navigation">
      <button
        ref={buttonRef}
        onClick={toggleMenu}
        aria-expanded={isMenuOpen}
        aria-controls="main-menu"
        aria-haspopup="true"
        className="menu-toggle"
      >
        <span className="sr-only">
          {isMenuOpen ? 'Close' : 'Open'} main menu
        </span>
        <span aria-hidden="true"></span>
      </button>

      <ul
        ref={menuRef}
        id="main-menu"
        className={`main-menu ${isMenuOpen ? 'open' : ''}`}
        onKeyDown={handleKeyDown}
        role="menubar"
      >
        <li role="none">
          <a href="/" role="menuitem" tabIndex={isMenuOpen ? 0 : -1}>
            Home
          </a>
        </li>
        <li role="none">
          <a href="/about" role="menuitem" tabIndex={isMenuOpen ? 0 : -1}>
            About
          </a>
        </li>
        <li role="none">
          <a href="/contact" role="menuitem" tabIndex={isMenuOpen ? 0 : -1}>
            Contact
          </a>
        </li>
      </ul>
    </nav>
  );
};

5. Accessible Data Tables:

interface TableData {
  id: string;
  name: string;
  email: string;
  role: string;
  status: 'active' | 'inactive';
}

const AccessibleTable: React.FC<{ data: TableData[] }> = ({ data }) => {
  const [sortConfig, setSortConfig] = useState<{
    key: keyof TableData;
    direction: 'asc' | 'desc';
  } | null>(null);

  const handleSort = (key: keyof TableData) => {
    setSortConfig(prev => ({
      key,
      direction: prev?.key === key && prev.direction === 'asc' ? 'desc' : 'asc',
    }));
  };

  const sortedData = useMemo(() => {
    if (!sortConfig) return data;
    
    return [...data].sort((a, b) => {
      const aVal = a[sortConfig.key];
      const bVal = b[sortConfig.key];
      
      if (aVal < bVal) return sortConfig.direction === 'asc' ? -1 : 1;
      if (aVal > bVal) return sortConfig.direction === 'asc' ? 1 : -1;
      return 0;
    });
  }, [data, sortConfig]);

  return (
    <div className="table-container">
      <table role="table" aria-label="User data table">
        <caption className="sr-only">
          User data table with sortable columns
        </caption>
        <thead>
          <tr role="row">
            <th
              role="columnheader"
              tabIndex={0}
              onClick={() => handleSort('name')}
              onKeyDown={(e) => e.key === 'Enter' && handleSort('name')}
              aria-sort={
                sortConfig?.key === 'name'
                  ? sortConfig.direction === 'asc' ? 'ascending' : 'descending'
                  : 'none'
              }
            >
              Name
              <span className="sr-only">
                {sortConfig?.key === 'name'
                  ? `Sorted ${sortConfig.direction === 'asc' ? 'ascending' : 'descending'}`
                  : 'Click to sort'}
              </span>
            </th>
            <th
              role="columnheader"
              tabIndex={0}
              onClick={() => handleSort('email')}
              onKeyDown={(e) => e.key === 'Enter' && handleSort('email')}
              aria-sort={
                sortConfig?.key === 'email'
                  ? sortConfig.direction === 'asc' ? 'ascending' : 'descending'
                  : 'none'
              }
            >
              Email
            </th>
            <th
              role="columnheader"
              tabIndex={0}
              onClick={() => handleSort('role')}
              onKeyDown={(e) => e.key === 'Enter' && handleSort('role')}
              aria-sort={
                sortConfig?.key === 'role'
                  ? sortConfig.direction === 'asc' ? 'ascending' : 'descending'
                  : 'none'
              }
            >
              Role
            </th>
            <th
              role="columnheader"
              tabIndex={0}
              onClick={() => handleSort('status')}
              onKeyDown={(e) => e.key === 'Enter' && handleSort('status')}
              aria-sort={
                sortConfig?.key === 'status'
                  ? sortConfig.direction === 'asc' ? 'ascending' : 'descending'
                  : 'none'
              }
            >
              Status
            </th>
          </tr>
        </thead>
        <tbody>
          {sortedData.map((row) => (
            <tr key={row.id} role="row">
              <td role="cell">{row.name}</td>
              <td role="cell">{row.email}</td>
              <td role="cell">{row.role}</td>
              <td role="cell">
                <span
                  className={`status-badge ${row.status}`}
                  aria-label={`Status: ${row.status}`}
                >
                  {row.status}
                </span>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

6. Custom Accessibility Hooks:

// Focus management hook
const useFocusManagement = () => {
  const focusableElements = useRef<HTMLElement[]>([]);
  
  const trapFocus = (container: HTMLElement) => {
    const focusable = container.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    ) as NodeListOf<HTMLElement>;
    
    focusableElements.current = Array.from(focusable);
    
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Tab') {
        const firstElement = focusableElements.current[0];
        const lastElement = focusableElements.current[focusableElements.current.length - 1];
        
        if (e.shiftKey) {
          if (document.activeElement === firstElement) {
            lastElement.focus();
            e.preventDefault();
          }
        } else {
          if (document.activeElement === lastElement) {
            firstElement.focus();
            e.preventDefault();
          }
        }
      }
    };
    
    container.addEventListener('keydown', handleKeyDown);
    
    return () => {
      container.removeEventListener('keydown', handleKeyDown);
    };
  };
  
  return { trapFocus };
};

// Screen reader announcements
const useScreenReaderAnnouncement = () => {
  const announce = useCallback((message: string, priority: 'polite' | 'assertive' = 'polite') => {
    const announcement = document.createElement('div');
    announcement.setAttribute('aria-live', priority);
    announcement.setAttribute('aria-atomic', 'true');
    announcement.className = 'sr-only';
    announcement.textContent = message;
    
    document.body.appendChild(announcement);
    
    setTimeout(() => {
      document.body.removeChild(announcement);
    }, 1000);
  }, []);
  
  return { announce };
};

7. Best Practices:

Internationalization:

  • Use semantic keys for translations
  • Support pluralization and interpolation
  • Handle RTL languages properly
  • Test with different text lengths
  • Use proper date/number formatting
  • Consider cultural differences in UI patterns

Accessibility:

  • Use semantic HTML elements
  • Provide proper ARIA labels and roles
  • Ensure keyboard navigation works
  • Maintain proper focus management
  • Test with screen readers
  • Use sufficient color contrast
  • Provide alternative text for images
  • Make interactive elements large enough
  • Use proper heading hierarchy
  • Test with real users with disabilities

What tools and processes do you use for maintaining code quality? (ESLint, Prettier, Husky, CI/CD, etc.)

Answer:

1. ESLint Configuration:

// .eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/recommended",
    "plugin:import/recommended",
    "plugin:import/typescript",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2022,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "plugins": [
    "@typescript-eslint",
    "react",
    "react-hooks",
    "jsx-a11y",
    "import"
  ],
  "rules": {
    "react/react-in-jsx-scope": "off",
    "react/prop-types": "off",
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "warn",
    "@typescript-eslint/no-explicit-any": "warn",
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn",
    "jsx-a11y/anchor-is-valid": "error",
    "import/order": [
      "error",
      {
        "groups": [
          "builtin",
          "external",
          "internal",
          "parent",
          "sibling",
          "index"
        ],
        "newlines-between": "always"
      }
    ]
  },
  "settings": {
    "react": {
      "version": "detect"
    },
    "import/resolver": {
      "typescript": {
        "alwaysTryTypes": true
      }
    }
  }
}

2. Prettier Configuration:

// .prettierrc
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "bracketSpacing": true,
  "bracketSameLine": false,
  "arrowParens": "avoid",
  "endOfLine": "lf",
  "quoteProps": "as-needed",
  "jsxSingleQuote": true,
  "proseWrap": "preserve"
}
// .prettierignore
node_modules/
dist/
build/
.next/
coverage/
*.min.js
*.min.css
package-lock.json
yarn.lock

3. Husky Git Hooks:

// package.json
{
  "scripts": {
    "lint": "eslint src --ext .ts,.tsx --max-warnings 0",
    "lint:fix": "eslint src --ext .ts,.tsx --fix",
    "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
    "type-check": "tsc --noEmit",
    "test": "jest",
    "test:coverage": "jest --coverage",
    "test:watch": "jest --watch",
    "build": "tsc && vite build",
    "prepare": "husky install"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "pre-push": "npm run type-check && npm run test"
    }
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{js,jsx,json,css,md}": [
      "prettier --write"
    ]
  }
}

4. TypeScript Configuration:

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["DOM", "DOM.Iterable", "ES6"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "baseUrl": "src",
    "paths": {
      "@/*": ["*"],
      "@/components/*": ["components/*"],
      "@/hooks/*": ["hooks/*"],
      "@/utils/*": ["utils/*"],
      "@/types/*": ["types/*"]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "build"
  ]
}

5. Jest Testing Configuration:

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/index.tsx',
    '!src/setupTests.ts',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.{ts,tsx}',
    '<rootDir>/src/**/*.{test,spec}.{ts,tsx}',
  ],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
};
// src/setupTests.ts
import '@testing-library/jest-dom';
import { configure } from '@testing-library/react';

// Configure testing library
configure({ testIdAttribute: 'data-testid' });

// Mock IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
  constructor() {}
  disconnect() {}
  observe() {}
  unobserve() {}
};

// Mock ResizeObserver
global.ResizeObserver = class ResizeObserver {
  constructor() {}
  disconnect() {}
  observe() {}
  unobserve() {}
};

6. CI/CD Pipeline (GitHub Actions):

# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  quality-checks:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Type checking
        run: npm run type-check
        
      - name: Linting
        run: npm run lint
        
      - name: Format checking
        run: npm run format:check
        
      - name: Run tests
        run: npm run test:coverage
        
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
          
  build:
    needs: quality-checks
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build application
        run: npm run build
        
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-files
          path: dist/
          
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-files
          path: dist/
          
      - name: Deploy to production
        run: |
          # Deployment commands here
          echo "Deploying to production..."

7. Additional Quality Tools:

// package.json - Additional dev dependencies
{
  "devDependencies": {
    "@commitlint/cli": "^17.0.0",
    "@commitlint/config-conventional": "^17.0.0",
    "commitizen": "^4.2.4",
    "cz-conventional-changelog": "^3.3.0",
    "husky": "^8.0.0",
    "lint-staged": "^13.0.0",
    "npm-run-all": "^4.1.5",
    "plop": "^3.0.0",
    "size-limit": "^8.0.0",
    "@size-limit/preset-small-lib": "^8.0.0"
  }
}
// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'style',
        'refactor',
        'perf',
        'test',
        'chore',
        'ci',
        'build',
        'revert'
      ]
    ]
  }
};
// .size-limit.json
[
  {
    "path": "dist/index.js",
    "limit": "10 KB"
  },
  {
    "path": "dist/index.css",
    "limit": "2 KB"
  }
]

8. VS Code Configuration:

// .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.organizeImports": true
  },
  "typescript.preferences.importModuleSpecifier": "relative",
  "typescript.suggest.autoImports": true,
  "emmet.includeLanguages": {
    "typescript": "html",
    "typescriptreact": "html"
  },
  "files.associations": {
    "*.css": "tailwindcss"
  }
}
// .vscode/extensions.json
{
  "recommendations": [
    "esbenp.prettier-vscode",
    "dbaeumer.vscode-eslint",
    "bradlc.vscode-tailwindcss",
    "ms-vscode.vscode-typescript-next",
    "formulahendry.auto-rename-tag",
    "christian-kohler.path-intellisense",
    "ms-vscode.vscode-json"
  ]
}

9. Automated Code Generation:

// plopfile.js
module.exports = function (plop) {
  plop.setGenerator('component', {
    description: 'Create a new React component',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'Component name:',
      },
      {
        type: 'confirm',
        name: 'withProps',
        message: 'Include props interface?',
        default: true,
      },
      {
        type: 'confirm',
        name: 'withTests',
        message: 'Include test file?',
        default: true,
      },
    ],
    actions: [
      {
        type: 'add',
        path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.tsx',
        templateFile: 'templates/component.hbs',
      },
      {
        type: 'add',
        path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.test.tsx',
        templateFile: 'templates/component.test.hbs',
        skip: (data) => !data.withTests,
      },
      {
        type: 'add',
        path: 'src/components/{{pascalCase name}}/index.ts',
        templateFile: 'templates/index.hbs',
      },
    ],
  });
};

10. Best Practices Summary:

Code Quality Tools:

  • ESLint: Catch bugs and enforce coding standards
  • Prettier: Consistent code formatting
  • TypeScript: Type safety and better developer experience
  • Husky: Git hooks for pre-commit checks
  • lint-staged: Run linters only on staged files
  • Jest: Unit testing with coverage reports
  • Commitlint: Enforce conventional commit messages

Processes:

  • Pre-commit hooks: Automatic linting and formatting
  • CI/CD pipeline: Automated testing and deployment
  • Code reviews: Peer review process with quality gates
  • Automated testing: Unit, integration, and E2E tests
  • Performance monitoring: Bundle size limits and performance budgets
  • Documentation: Automated API documentation generation

Quality Metrics:

  • Code coverage thresholds (80%+)
  • Bundle size limits
  • Performance budgets
  • Accessibility compliance
  • Security vulnerability scanning
  • Dependency audit and updates