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.

Resources