Building government applications with React and TypeScript requires balancing modern development practices with stringent requirements for accessibility, security, and reliability. Here’s what I’ve learned.
Why TypeScript for Government Work
TypeScript isn’t just nice to have—it’s essential for government applications:
Type Safety Prevents Costly Errors
// Bad: Runtime error waiting to happen
function processApplication(data) {
return data.applicant.name.toUpperCase();
}
// Good: Compile-time error catches issues early
interface Application {
applicant: {
name: string;
email: string;
};
submittedAt: Date;
}
function processApplication(data: Application): string {
return data.applicant.name.toUpperCase();
}
Self-Documenting Code
Government applications have long lifespans. TypeScript serves as living documentation:
interface PublicRecordsRequest {
id: string;
requestor: Requestor;
agency: Agency;
status: RequestStatus;
submittedDate: Date;
dueDate: Date;
records?: Document[];
}
type RequestStatus =
| 'submitted'
| 'in_review'
| 'fulfilled'
| 'denied'
| 'appealed';
Component Architecture
Keep Components Focused
Each component should have a single responsibility:
// ❌ Too many responsibilities
function ApplicationDashboard() {
// Fetches data
// Handles auth
// Manages state
// Renders complex UI
// Handles form submission
}
// ✅ Focused components
function ApplicationDashboard() {
return (
<Layout>
<ApplicationList applications={applications} />
<ApplicationFilters onFilter={handleFilter} />
<ApplicationStats data={stats} />
</Layout>
);
}
Use Composition Over Inheritance
interface BaseProps {
className?: string;
'aria-label'?: string;
}
interface ButtonProps extends BaseProps {
onClick: () => void;
variant: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
}
function Button({ variant, onClick, children, ...props }: ButtonProps) {
return (
<button
className={`btn btn-${variant} ${props.className || ''}`}
onClick={onClick}
aria-label={props['aria-label']}
disabled={props.disabled}
>
{children}
</button>
);
}
State Management
Use the Right Tool
Not every application needs Redux. For government apps:
Local State (useState): Form inputs, UI toggles Context: Theme, auth, user preferences React Query: Server state, caching Zustand/Redux: Complex global state
// Form state - local useState
function ApplicationForm() {
const [formData, setFormData] = useState<FormData>(initialData);
// Server state - React Query
const { data: agencies } = useQuery({
queryKey: ['agencies'],
queryFn: fetchAgencies,
});
// Global state - Context
const { user } = useAuth();
}
Accessibility is Non-Negotiable
Government applications must meet WCAG 2.1 AA standards minimum:
Semantic HTML
// ❌ Divs everywhere
<div onClick={handleClick}>Click me</div>
// ✅ Semantic elements
<button onClick={handleClick} type="button">
Click me
</button>
ARIA Attributes
function SearchResults({ results, loading }: Props) {
return (
<div role="region" aria-live="polite" aria-busy={loading}>
{loading ? (
<div role="status">Loading results...</div>
) : (
<ul aria-label="Search results">
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
)}
</div>
);
}
Keyboard Navigation
function Dialog({ isOpen, onClose, children }: DialogProps) {
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
if (isOpen) {
document.addEventListener('keydown', handleEscape);
// Trap focus within dialog
return () => document.removeEventListener('keydown', handleEscape);
}
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div role="dialog" aria-modal="true">
{children}
</div>
);
}
Security Considerations
Input Validation
Always validate on both client and server:
import { z } from 'zod';
const ApplicationSchema = z.object({
email: z.string().email(),
ssn: z.string().regex(/^\d{3}-\d{2}-\d{4}$/),
income: z.number().positive().max(10000000),
});
type Application = z.infer<typeof ApplicationSchema>;
function validateApplication(data: unknown): Application {
return ApplicationSchema.parse(data);
}
Sanitize User Input
import DOMPurify from 'dompurify';
function UserComment({ comment }: Props) {
const sanitized = DOMPurify.sanitize(comment);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
Secure API Calls
async function fetchProtectedData(token: string) {
const response = await fetch('/api/data', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
credentials: 'same-origin', // CSRF protection
});
if (!response.ok) {
throw new Error('Failed to fetch data');
}
return response.json();
}
Performance Optimization
Code Splitting
import { lazy, Suspense } from 'react';
const AdminPanel = lazy(() => import('./AdminPanel'));
function App() {
return (
<Suspense fallback={<Loading />}>
{user.isAdmin && <AdminPanel />}
</Suspense>
);
}
Memoization
function ExpensiveList({ items }: Props) {
const sortedItems = useMemo(
() => items.sort((a, b) => a.date.getTime() - b.date.getTime()),
[items]
);
return (
<ul>
{sortedItems.map(item => (
<ExpensiveItem key={item.id} item={item} />
))}
</ul>
);
}
const ExpensiveItem = memo(({ item }: ItemProps) => {
// Expensive rendering logic
return <li>{item.name}</li>;
});
Error Handling
Error Boundaries
class ErrorBoundary extends React.Component<Props, State> {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Log to monitoring service
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div role="alert">
<h1>Something went wrong</h1>
<p>Please contact support if this persists.</p>
</div>
);
}
return this.props.children;
}
}
Graceful Degradation
function DataTable() {
const { data, error, isLoading } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
retry: 3,
retryDelay: 1000,
});
if (error) {
return (
<Alert severity="error">
Unable to load data. Please try again later.
</Alert>
);
}
if (isLoading) {
return <Skeleton variant="table" />;
}
return <Table data={data} />;
}
Testing Strategy
Unit Tests
import { render, screen, fireEvent } from '@testing-library/react';
describe('ApplicationForm', () => {
it('validates required fields', async () => {
render(<ApplicationForm />);
const submitButton = screen.getByRole('button', { name: /submit/i });
fireEvent.click(submitButton);
expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
});
});
Integration Tests
import { renderHook, waitFor } from '@testing-library/react';
describe('useApplicationData', () => {
it('fetches and caches applications', async () => {
const { result } = renderHook(() => useApplicationData());
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toHaveLength(10);
});
});
Production Checklist
Before deploying government applications:
- TypeScript strict mode enabled
- All accessibility tests passing
- Security audit completed
- Performance benchmarks met
- Error monitoring configured
- Analytics implementation reviewed
- Documentation updated
- Backup and disaster recovery tested
Real-World Impact
These practices enabled our team to:
- Reduce bugs by 60% through type safety
- Achieve WCAG AA compliance on first audit
- Handle 10,000+ concurrent users during peak times
- Pass security reviews without major findings
- Onboard new developers in days instead of weeks
Conclusion
Government applications require more rigor than typical web apps. TypeScript and React provide the foundation, but success requires discipline, attention to detail, and commitment to serving all users effectively.
The stakes are high—these applications affect people’s lives. Build accordingly.