Skip to content

State Management

Carbon Connect uses React Query (TanStack Query) for server state management and custom hooks for UI state.


React Query

React Query manages all server state: data fetching, caching, background updates, and optimistic mutations.

Provider Setup

The providers.tsx file at the app root configures React Query:

// src/app/providers.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,     // 5 minutes
      gcTime: 30 * 60 * 1000,        // 30 minutes
      retry: 2,
      refetchOnWindowFocus: false,
    },
  },
});

API Hooks

All React Query hooks are organized by domain in src/lib/api/hooks/:

Hook Catalog

Module Hooks Domain
useAuth useCurrentUser, useLogin, useRegister, useLogout, useUpdateProfile, useChangePassword, useIsAuthenticated Authentication
useCompanies useCompanies, useCompany, useCreateCompany, useUpdateCompany, useDeleteCompany, useUploadCompanyLogo Company management
useGrants useGrants, useGrant, useGrantSearch, useInfiniteGrants, useInfiniteGrantSearch, useSimilarGrants, useGrantSources, useUpcomingDeadlines, useGrantStats, useCarbonGrants Grant search
useMatches useMatches, useMatch, useSavedMatches, useMatchStats, useCalculateMatches, useSaveMatch, useDismissMatch, useRestoreMatch Grant matching
useApplications useApplications, useApplication, useApplicationsByStatus, useCreateApplication, useUpdateApplication, useDeleteApplication, useGenerateContent, useUploadDocument Applications
useDashboard useDashboardStats, useRecentActivity Dashboard
useReference useNaceCodes, useCountries, useEuCountries Reference data
useCarbonProfile useCarbonProfile, useUpdateCarbonProfile, useCalculateEmissions, useResetCarbonProfile, useCarbonRecommendedGrants Carbon profile
useVcri useVcriProjects, useVcriProject, useCreateVcriProject, useSetVcriBaseline, useCreateVcriMeasurement, useIssueVcriCertificate VCRI projects

Query Keys

Each module defines a queryKeys factory for cache management:

export const grantKeys = {
  all: ["grants"] as const,
  lists: () => [...grantKeys.all, "list"] as const,
  list: (params: GrantSearchParams) => [...grantKeys.lists(), params] as const,
  details: () => [...grantKeys.all, "detail"] as const,
  detail: (id: string) => [...grantKeys.details(), id] as const,
  search: (params: FullTextSearchParams) => [...grantKeys.all, "search", params] as const,
  stats: () => [...grantKeys.all, "stats"] as const,
  sources: () => [...grantKeys.all, "sources"] as const,
};

Query Hook Example

export function useGrants(params: GrantSearchParams = {}) {
  return useQuery({
    queryKey: grantKeys.list(params),
    queryFn: () => grantApi.search(params),
    staleTime: 5 * 60 * 1000,
  });
}

Mutation Hook Example

export function useCreateCompany() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: CompanyCreateRequest) => companyApi.create(data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: companyKeys.all });
    },
  });
}

Infinite Scroll

For paginated data with infinite scrolling:

export function useInfiniteGrants(params: GrantSearchParams = {}) {
  return useInfiniteQuery({
    queryKey: grantKeys.list({ ...params, infinite: true }),
    queryFn: ({ pageParam = 1 }) =>
      grantApi.search({ ...params, page: pageParam }),
    getNextPageParam: (lastPage) =>
      lastPage.page < lastPage.total_pages ? lastPage.page + 1 : undefined,
    initialPageParam: 1,
  });
}

API Client

Source: src/lib/api/client.ts

The API client provides a type-safe fetch wrapper with automatic token management:

Features

  • Automatic JWT token refresh on 401 responses
  • Token queue for concurrent 401 handling (prevents multiple refresh calls)
  • Type-safe methods -- get<T>(), post<T>(), patch<T>(), delete<T>()
  • Query parameter building from typed objects
  • Error normalization via ApiClientError class

Token Management

// Tokens stored in localStorage
getStoredTokens(): { accessToken, refreshToken }
storeTokens(tokens: AuthTokens): void
clearTokens(): void

Request Flow

sequenceDiagram
    participant C as Component
    participant H as React Query Hook
    participant A as API Client
    participant S as Backend API

    C->>H: useGrants({ country: "DE" })
    H->>A: apiClient.get("/api/v1/grants", params)
    A->>A: Add Bearer token from localStorage
    A->>S: GET /api/v1/grants?country=DE
    S-->>A: 200 OK + JSON
    A-->>H: Typed response
    H-->>C: { data, isLoading, error }

    Note over A,S: On 401 response:
    A->>S: POST /api/v1/auth/refresh
    S-->>A: New tokens
    A->>A: Store new tokens
    A->>S: Retry original request

Custom Hooks

Source: src/hooks/

Non-API hooks for UI behavior:

Hook Purpose
useToast Toast notification management
useKeyboardShortcuts Register keyboard shortcuts
useGlobalShortcuts Application-wide shortcuts (Cmd+K, etc.)
useReducedMotion Respect user's reduced motion preference
useMotionDuration Adaptive animation duration
useAnimatedCounter Animated number transitions
useScrollAnimation Intersection observer-based animations
useScrollProgress Scroll position tracking

Usage Example

import { useToast, useKeyboardShortcuts } from "@/hooks";

export function GrantDetail() {
  const { toast } = useToast();

  useKeyboardShortcuts([
    { key: "s", ctrl: true, handler: () => handleSave() },
    { key: "Escape", handler: () => handleClose() },
  ]);

  const handleSave = () => {
    toast({ title: "Grant saved", type: "success" });
  };
}

Endpoint Definitions

Source: src/lib/api/endpoints.ts

All API calls are defined in domain-specific objects:

Object Endpoint Prefix Methods
authApi /api/v1/auth login, register, logout, getCurrentUser
companyApi /api/v1/companies list, get, create, update, delete
grantApi /api/v1/grants search, fullTextSearch, get, getSimilar, getStats
matchApi /api/v1/matches getForCompany, get, calculate, updateAction
applicationApi /api/v1/applications list, get, create, update, delete, generateContent
dashboardApi /api/v1/dashboard getStats, getRecentActivity
referenceApi /api/v1/reference searchNaceCodes, getCountries
carbonProfileApi /api/v1/companies/{id}/carbon-profile get, update, calculate, reset
vcriApi /api/v1/vcri listProjects, createProject, setBaseline, createMeasurement