""" State Management Analyzer Analyzes React state management against Bulletproof React principles: - Appropriate tool for each state type (component, app, server, form, URL) - State localized when possible - Server cache separated (React Query/SWR) - No global state overuse """ import json import re from pathlib import Path from typing import Dict, List def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]: """ Analyze state management patterns. Args: codebase_path: Path to React codebase metadata: Project metadata from discovery phase Returns: List of findings with severity and migration guidance """ findings = [] tech_stack = metadata.get('tech_stack', {}) src_dir = codebase_path / 'src' if not src_dir.exists(): return findings # Check for appropriate state management tools findings.extend(check_state_management_tools(tech_stack)) # Check for data fetching library (server cache state) findings.extend(check_data_fetching_library(tech_stack)) # Check for form state management findings.extend(check_form_state_management(src_dir, tech_stack)) # Check for potential state management issues findings.extend(check_state_patterns(src_dir)) return findings def check_state_management_tools(tech_stack: Dict) -> List[Dict]: """Check for presence of appropriate state management tools.""" findings = [] # Check if any global state management is present has_state_mgmt = any([ tech_stack.get('redux'), tech_stack.get('zustand'), tech_stack.get('jotai'), tech_stack.get('mobx') ]) # If app has many features but no state management, might need it # (This is a heuristic - could be Context-based which is fine) if not has_state_mgmt: findings.append({ 'severity': 'low', 'category': 'state', 'title': 'No explicit global state management detected', 'current_state': 'No Redux, Zustand, Jotai, or MobX found', 'target_state': 'Consider Zustand or Jotai for global state if Context becomes complex. Start with Context + hooks.', 'migration_steps': [ 'Evaluate if Context API is sufficient for your needs', 'If Context becomes complex, consider Zustand (simple) or Jotai (atomic)', 'Avoid Redux unless you need its ecosystem (Redux Toolkit simplifies it)', 'Keep state as local as possible before going global' ], 'effort': 'low', }) return findings def check_data_fetching_library(tech_stack: Dict) -> List[Dict]: """Check for React Query, SWR, or similar for server state.""" findings = [] has_data_fetching = any([ tech_stack.get('react-query'), tech_stack.get('swr'), tech_stack.get('apollo'), tech_stack.get('rtk-query') ]) if not has_data_fetching: findings.append({ 'severity': 'high', 'category': 'state', 'title': 'No data fetching library detected', 'current_state': 'No React Query, SWR, Apollo Client, or RTK Query found', 'target_state': 'Use React Query or SWR for server state management (caching, refetching, optimistic updates)', 'migration_steps': [ 'Install React Query (@tanstack/react-query) or SWR', 'Wrap app with QueryClientProvider (React Query) or SWRConfig (SWR)', 'Convert fetch calls to useQuery hooks', 'Replace manual loading/error states with library patterns', 'Add staleTime, cacheTime configurations as needed' ], 'effort': 'medium', }) return findings def check_form_state_management(src_dir: Path, tech_stack: Dict) -> List[Dict]: """Check for form state management.""" findings = [] has_form_lib = any([ tech_stack.get('react-hook-form'), tech_stack.get('formik') ]) # Look for form components without form library if not has_form_lib: form_files = [] for file_path in src_dir.rglob('*.{tsx,jsx}'): try: with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # Look for
tags if re.search(r'\s]', content, re.IGNORECASE): form_files.append(str(file_path.relative_to(src_dir))) except: pass if len(form_files) > 3: # More than 3 forms suggests need for form library findings.append({ 'severity': 'medium', 'category': 'state', 'title': f'No form library but {len(form_files)} forms detected', 'current_state': f'{len(form_files)} form components without React Hook Form or Formik', 'target_state': 'Use React Hook Form for performant form state management', 'migration_steps': [ 'Install react-hook-form', 'Replace controlled form state with useForm() hook', 'Use register() for input registration', 'Handle validation with yup or zod schemas', 'Reduce re-renders with uncontrolled inputs' ], 'effort': 'medium', 'affected_files': form_files[:5], }) return findings def check_state_patterns(src_dir: Path) -> List[Dict]: """Check for common state management anti-patterns.""" findings = [] # Look for large Context providers (potential performance issue) large_contexts = [] for file_path in src_dir.rglob('*.{tsx,jsx}'): try: with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # Look for Context creation with many values if 'createContext' in content: # Count useState hooks in the provider state_count = len(re.findall(r'useState\s*\(', content)) if state_count > 5: large_contexts.append({ 'file': str(file_path.relative_to(src_dir)), 'state_count': state_count }) except: pass if large_contexts: for ctx in large_contexts: findings.append({ 'severity': 'medium', 'category': 'state', 'title': f'Large Context with {ctx["state_count"]} state values', 'current_state': f'{ctx["file"]} has many state values in one Context', 'target_state': 'Split large Contexts into smaller, focused Contexts to prevent unnecessary re-renders', 'migration_steps': [ 'Identify which state values change together', 'Create separate Contexts for independent state', 'Use Context composition for related state', 'Consider Zustand/Jotai for complex global state' ], 'effort': 'medium', 'file': ctx['file'], }) return findings