🤖 Recommended Agent: @typescript-react-agent
This issue is best handled by the TypeScript React Agent - an expert in React hooks and component reusability patterns.
🎯 Objective
Extract duplicate component logic into reusable custom hooks and utility functions to improve code maintainability, reduce duplication, and follow DRY principles for v1.0 release.
📋 Background
The TypeScript React Agent guidelines emphasize "MANDATORY Reusability" - always reuse/extend existing utilities, helpers, constants, and components before creating new ones. Analysis shows multiple widgets implement similar patterns (state management, data fetching, formatting) that could be extracted into reusable hooks, aligning with React best practices and project guidelines.
📊 Current State (Measured Metrics)
- 111 source files total in src/
- Pattern duplication identified:
- Multiple widgets use similar useState patterns for security level selection
- Duplicate formatting logic across multiple components
- Repeated data transformation patterns
- Similar loading/error state management
- Existing hooks:
src/hooks/ directory exists but underutilized
- Maintainability impact: Changes to common patterns require updates in multiple files
Common Patterns Found:
- Security Level State Management - Used in 10+ widget components
- Data Formatting - Currency, percentages, dates repeated across widgets
- Loading States - Similar loading/error patterns in multiple components
- Responsive Design Hooks - Window size/viewport detection duplicated
✅ Acceptance Criteria
🛠️ Implementation Guidance for @typescript-react-agent
Agent-Specific Instructions:
✅ Check existing hooks first in src/hooks/ before creating new ones
✅ Follow React hooks rules (only call at top level, proper dependency arrays)
✅ Use TypeScript generics for flexible, reusable hooks
✅ Add proper TypeScript types for hook parameters and return values
✅ Write tests for each hook using React Testing Library + Vitest
Hook #1: useSecurityLevelState (High Priority)
Problem: Many widgets duplicate security level state management.
Current Pattern (Duplicated):
// Found in multiple widgets
const [availabilityLevel, setAvailabilityLevel] = useState<SecurityLevel>('Moderate');
const [integrityLevel, setIntegrityLevel] = useState<SecurityLevel>('Moderate');
const [confidentialityLevel, setConfidentialityLevel] = useState<SecurityLevel>('Moderate');
Solution:
// src/hooks/useSecurityLevelState.ts
import { useState } from 'react';
import { SecurityLevel, CIAComponent } from '@/types/cia';
export interface SecurityLevelState {
availability: SecurityLevel;
integrity: SecurityLevel;
confidentiality: SecurityLevel;
}
export interface UseSecurityLevelStateReturn {
levels: SecurityLevelState;
setLevel: (component: CIAComponent, level: SecurityLevel) => void;
resetLevels: (defaultLevel?: SecurityLevel) => void;
}
/**
* Custom hook for managing CIA triad security levels
* @param initialLevels - Initial security levels (defaults to 'Moderate')
* @returns Security level state and update functions
*/
export function useSecurityLevelState(
initialLevels?: Partial<SecurityLevelState>
): UseSecurityLevelStateReturn {
const [levels, setLevels] = useState<SecurityLevelState>({
availability: initialLevels?.availability ?? 'Moderate',
integrity: initialLevels?.integrity ?? 'Moderate',
confidentiality: initialLevels?.confidentiality ?? 'Moderate',
});
const setLevel = (component: CIAComponent, level: SecurityLevel) => {
setLevels(prev => ({ ...prev, [component]: level }));
};
const resetLevels = (defaultLevel: SecurityLevel = 'Moderate') => {
setLevels({
availability: defaultLevel,
integrity: defaultLevel,
confidentiality: defaultLevel,
});
};
return { levels, setLevel, resetLevels };
}
Usage:
// In widget components
const { levels, setLevel } = useSecurityLevelState();
<SecurityLevelSelector
component="availability"
level={levels.availability}
onChange={(level) => setLevel('availability', level)}
/>
Hook #2: useFormattedMetrics
Problem: Duplicate formatting logic for currency, percentages, and numbers.
// src/hooks/useFormattedMetrics.ts
import { useMemo } from 'react';
import { formatCurrency, formatPercentage } from '@/utils/formatUtils';
export interface MetricFormatters {
currency: (value: number) => string;
percentage: (value: number) => string;
number: (value: number) => string;
}
/**
* Provides memoized formatting functions for metrics
* @param locale - Locale string (default: 'en-US')
* @returns Formatting functions for common metric types
*/
export function useFormattedMetrics(locale = 'en-US'): MetricFormatters {
return useMemo(() => ({
currency: (value: number) => formatCurrency(value, locale),
percentage: (value: number) => formatPercentage(value),
number: (value: number) => value.toLocaleString(locale),
}), [locale]);
}
Hook #3: useComponentData
Problem: Multiple widgets fetch/transform component data similarly.
// src/hooks/useComponentData.ts
import { useMemo } from 'react';
import { CIAComponent, SecurityLevel } from '@/types/cia';
import { getComponentMetrics } from '@/services/securityMetricsService';
export interface UseComponentDataOptions {
component: CIAComponent;
level: SecurityLevel;
includeMetrics?: boolean;
}
/**
* Fetches and transforms component data with memoization
* @param options - Component data options
* @returns Memoized component data
*/
export function useComponentData(options: UseComponentDataOptions) {
const { component, level, includeMetrics = true } = options;
const data = useMemo(() => {
if (!includeMetrics) {
return { component, level };
}
const metrics = getComponentMetrics(component, level);
return { component, level, metrics };
}, [component, level, includeMetrics]);
return data;
}
Hook #4: useResponsiveBreakpoint
Problem: Multiple components check window size for responsive design.
// src/hooks/useResponsiveBreakpoint.ts
import { useState, useEffect } from 'react';
export type Breakpoint = 'mobile' | 'tablet' | 'desktop';
const BREAKPOINTS = {
mobile: 640,
tablet: 1024,
} as const;
/**
* Detects current responsive breakpoint
* @returns Current breakpoint name
*/
export function useResponsiveBreakpoint(): Breakpoint {
const [breakpoint, setBreakpoint] = useState<Breakpoint>(() => {
if (typeof window === 'undefined') return 'desktop';
const width = window.innerWidth;
if (width < BREAKPOINTS.mobile) return 'mobile';
if (width < BREAKPOINTS.tablet) return 'tablet';
return 'desktop';
});
useEffect(() => {
const handleResize = () => {
const width = window.innerWidth;
if (width < BREAKPOINTS.mobile) setBreakpoint('mobile');
else if (width < BREAKPOINTS.tablet) setBreakpoint('tablet');
else setBreakpoint('desktop');
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return breakpoint;
}
Hook #5: useLocalStorage (Optional)
Problem: Widgets may want to persist user preferences.
// src/hooks/useLocalStorage.ts
import { useState, useEffect } from 'react';
/**
* Syncs state with localStorage
* @param key - localStorage key
* @param initialValue - Initial value if key doesn't exist
* @returns Stateful value and setter function
*/
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = (value: T) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Error saving to localStorage', error);
}
};
return [storedValue, setValue];
}
Testing Pattern for Hooks:
// src/hooks/useSecurityLevelState.test.ts
import { renderHook, act } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { useSecurityLevelState } from './useSecurityLevelState';
describe('useSecurityLevelState', () => {
it('initializes with default Moderate levels', () => {
const { result } = renderHook(() => useSecurityLevelState());
expect(result.current.levels.availability).toBe('Moderate');
expect(result.current.levels.integrity).toBe('Moderate');
expect(result.current.levels.confidentiality).toBe('Moderate');
});
it('allows setting individual component levels', () => {
const { result } = renderHook(() => useSecurityLevelState());
act(() => {
result.current.setLevel('availability', 'High');
});
expect(result.current.levels.availability).toBe('High');
expect(result.current.levels.integrity).toBe('Moderate');
});
it('resets all levels to specified default', () => {
const { result } = renderHook(() => useSecurityLevelState());
act(() => {
result.current.setLevel('availability', 'High');
result.current.resetLevels('Low');
});
expect(result.current.levels.availability).toBe('Low');
});
});
Verification:
# Run tests for new hooks
npm test -- src/hooks
# Check test coverage
npm run coverage -- src/hooks
# Run all tests
npm test
# Run linter
npm run lint
🔗 Related Resources
📊 Metadata
Agent: @typescript-react-agent | Priority: Medium | Effort: M (6-8h)
Labels: type:refactor, domain:code-quality, priority:medium, size:medium, v1.0
🤖 Recommended Agent: @typescript-react-agent
🎯 Objective
Extract duplicate component logic into reusable custom hooks and utility functions to improve code maintainability, reduce duplication, and follow DRY principles for v1.0 release.
📋 Background
The TypeScript React Agent guidelines emphasize "MANDATORY Reusability" - always reuse/extend existing utilities, helpers, constants, and components before creating new ones. Analysis shows multiple widgets implement similar patterns (state management, data fetching, formatting) that could be extracted into reusable hooks, aligning with React best practices and project guidelines.
📊 Current State (Measured Metrics)
src/hooks/directory exists but underutilizedCommon Patterns Found:
✅ Acceptance Criteria
use*)🛠️ Implementation Guidance for @typescript-react-agent
Agent-Specific Instructions:
✅ Check existing hooks first in
src/hooks/before creating new ones✅ Follow React hooks rules (only call at top level, proper dependency arrays)
✅ Use TypeScript generics for flexible, reusable hooks
✅ Add proper TypeScript types for hook parameters and return values
✅ Write tests for each hook using React Testing Library + Vitest
Hook #1: useSecurityLevelState (High Priority)
Problem: Many widgets duplicate security level state management.
Current Pattern (Duplicated):
Solution:
Usage:
Hook #2: useFormattedMetrics
Problem: Duplicate formatting logic for currency, percentages, and numbers.
Hook #3: useComponentData
Problem: Multiple widgets fetch/transform component data similarly.
Hook #4: useResponsiveBreakpoint
Problem: Multiple components check window size for responsive design.
Hook #5: useLocalStorage (Optional)
Problem: Widgets may want to persist user preferences.
Testing Pattern for Hooks:
Verification:
🔗 Related Resources
src/hooks/src/utils/src/types/cia.ts,src/types/widgets.ts📊 Metadata
Agent: @typescript-react-agent | Priority: Medium | Effort: M (6-8h)
Labels:
type:refactor,domain:code-quality,priority:medium,size:medium,v1.0