State & Data Flow

FT Configs UI separates state into three layers:

  • Server state — managed by TanStack Query (@tanstack/react-query) over the shared apiService axios wrapper.

  • Global app state — React Context providers (I18nProvider, AuthProvider, AdminRolesProvider).

  • Local/form state — component-local useState plus react-hook-form + Zod for validated forms.

Provider hierarchy

Mounted in src/main.tsx / App.tsx:

  1. I18nProvider — outermost (Auth and Query error messages need t()).

  2. QueryProvider — supplies the TanStack Query client.

  3. AuthProvider — session state and derived flags.

  4. RouterProvider — TanStack Router (createRouter({ routeTree, defaultPreload: 'intent' }) from src/router.tsx).

  5. <Toaster/> — shadcn/ui toast host.

AdminRolesProvider is mounted locally in src/routes/_protected/settings/_admin.tsx, not globally, so the admin role list is only fetched when entering admin pages.

React Query configuration

Location: src/providers/query-provider.tsx.

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 30_000,        // 30s — avoid duplicate fetches on rapid navigation
        gcTime: 5 * 60_000,       // 5m — cached data retained after last observer unmounts
        retry: 1,                  // one automatic retry on transient failure
        refetchOnWindowFocus: false,
      },
    },
  });
}

// Browser singleton — prevents re-creation across HMR / React Strict double-render.
let browserQueryClient: QueryClient | undefined;
export function getQueryClient() {
  if (!browserQueryClient) browserQueryClient = makeQueryClient();
  return browserQueryClient;
}

<ReactQueryDevtools initialIsOpen={false} /> is mounted inside QueryProvider for development.

Feature hooks wrap apiService calls with useQuery / useMutation. React Query handles dedup, stale-while-revalidate, cancellation on unmount, and cache invalidation — do not add ad-hoc AbortController plumbing unless you need cross-effect coordination.

Auth / session flow

  1. App mounts AuthProvider (src/contexts/auth-provider.tsx).

  2. On mount, the provider calls refreshSession() which invokes api.getSession() (src/lib/api.ts) → GET /auth/me. A 401 is swallowed and yields { user: null, isAuthenticated: false }.

  3. Provider exposes:

    • user, isAuthenticated, isLoading

    • isReadOnly — computed as user?.role === 'VIEWER'

    • methods: login, firstLogin, changePassword, register, updateProfile, updateLocale, logout, refreshSession

  4. Provider calls setCurrentUserRole(user.role) (src/lib/permissions.ts) so the axios request interceptor can block write calls for read-only users.

  5. Side effects on session load: emits a read-only toast (with a dedup flag) and syncs locale when frontend/backend disagree.

  6. logout() is best-effort: api.logout() inside try/catch → clear state → useNavigate(LOGIN).

The route guard in _protected.tsx consumes useAuth() and redirects unauthenticated users to /login. See Routing & Layouts for why the guard runs in the component tree and not in beforeLoad.

Localization flow

  • I18nProvider statically imports locales/en.json and locales/ru.json at build time (no async loading).

  • detectLocale() resolves in order: cookie locale=(en|ru)navigator.language (startsWith ru) → 'en'.

  • setLocale(locale) updates state, writes localStorage, sets cookie locale=…; max-age=31536000, and syncs document.documentElement.lang.

  • t(key, fallbackOrParams?, params?) — nested lookup with plural support ({count {one … other …}}) and {{placeholder}} interpolation.

  • Context value: { locale, setLocale, t, loading: false }.

Form validation schemas are built via useLocalizedSchema() so Zod messages re-render on locale change.

AdminRolesProvider

Mounted lazily in _admin.tsx:

  • State: roles: AdminRole[], isLoading.

  • Module-level cachedRoles and loadingPromise dedupe parallel refresh calls.

  • refresh(force?)adminUsersApi.getRoles().

  • Errors flow through handleError() → localized toast.

  • useAdminRoles() hook includes a null-check to catch misuse outside the admin layout.

Data access flow

Component
  │   useXxxQuery() / useXxxMutation()   ← TanStack Query
  ▼
apiService (src/lib/axios.ts wrapper)    ← get/post/put/patch/delete
  │   axios request interceptors         ← baseURL, locale, read-only check, CSRF
  ▼
Backend  (/configs-service/*)
  │
  ▼
axios response interceptor               ← 401 refresh-token retry
  │
  ▼
React Query cache                        ← dedup + stale-while-revalidate
  │
  ▼
Component re-render                      ← typed DTOs from src/lib/definitions.ts

Error handling is unified via useErrorHandler().handleError(error, options), which extracts a UnifiedError (server / network / validation / frontend / unknown), shows a localized toast, and logs a grouped console entry. See API Integration for the full error classification.