Skip to content

TanStack Query (Vue Query) ​

Overview ​

The Brand Portal uses TanStack Vue Query (v5) for server state management: caching, request deduplication, background refetching, and mutations with cache invalidation. All Query-related composables and utilities live in app/composables/api/ alongside the imperative use*Api composables.

When to Use Queries vs API Composables ​

Use caseUseReason
Cached, reactive data (dashboard, folders, profile, sharing list)use*Queries composablesCaching, deduplication, loading/error state, refetch on focus
One-off or imperative calls (login, download, submit form)use*Api composablesNo cache needed; direct await
Cache invalidation after a mutationuseQueryClient() + queryClient.invalidateQueries({ queryKey })Keep UI in sync
Custom query/mutation with shared error handlinguseQuery/useMutation + queryKeys + createQueryFn/createMutationFnConsistency with global plugin

Plugin Configuration ​

The Vue Query plugin configures the global QueryClient:

  • staleTime: From NUXT_PUBLIC_QUERY_STALE_TIME (default 5 minutes). Used by getDefaultStaleTime() in useQueryClient.
  • Global error handling: 401 triggers logout; other errors show snackbar via getApiErrorMessage from ~/utils/error.
  • SSR: Hydrate/dehydrate so server-fetched data is available on the client without refetch.

queryKeys ​

Path: app/composables/api/queryKeys.ts

Centralized query key factory for type-safe cache invalidation. All keys should be defined here.

Structure:

  • dashboard: all, data(workspaceId, instanceId)
  • tiles: all, list(workspaceId)
  • banners: all, list(workspaceId, instanceId)
  • folders: all, list(workspaceId, subfolderList)
  • brand: all, details(brandName)
  • auth: all, user, session
  • account: all, status(workspaceId)
  • assets: all, details(assetId, workspaceId)
  • folderDetails: all, details(params), count(folderId, workspaceId)
  • collections: all, list(workspaceId), details(collectionId, params)
  • sharing: all, list(params)

Usage:

typescript
import { queryKeys } from '~/composables/api/queryKeys'

queryClient.invalidateQueries({ queryKey: queryKeys.dashboard.all })
queryClient.getQueryData(queryKeys.folderDetails.details(params))

useQueryClient ​

Path: app/composables/api/useQueryClient.ts

Provides access to the Vue Query QueryClient and helpers:

  • getDefaultStaleTime(overrideMs?): Returns NUXT_PUBLIC_QUERY_STALE_TIME or 5 minutes (for use in staleTime option).
  • createQueryFn(apiCall): Wraps an API call for use as queryFn; errors are rethrown (handled by plugin).
  • createQueryFnWithParams(apiCall): Same for parameterized API calls.
  • serializeQueryParams(params): Serializes params for stable query keys (used by queryKeys).

Use useQueryClient() from @tanstack/vue-query to get the QueryClient instance for invalidateQueries, setQueryData, etc.

useMutationClient ​

Path: app/composables/api/useMutationClient.ts

Utilities for mutations:

  • createMutationFn(apiCall): Wraps an API call for use as mutationFn; errors rethrown (handled by plugin).
  • invalidateQueries(queryClient, queryKeys): Invalidates given query keys after a successful mutation.
  • Helpers for optimistic updates with rollback (see source).

Usage:

typescript
import { useMutation } from '@tanstack/vue-query'
import { createMutationFn, invalidateQueries } from '~/composables/api/useMutationClient'
import { queryKeys } from '~/composables/api/queryKeys'

const queryClient = useQueryClient()
const mutation = useMutation({
  mutationFn: createMutationFn((data) => sharingApi.createLink(data)),
  onSuccess: () => {
    invalidateQueries(queryClient, queryKeys.sharing.all)
  },
})

use*Queries Composables ​

Each composable wraps one or more useQuery/useMutation calls and exposes reactive data and methods. They use queryKeys and the same API layer as use*Api so cache is shared (e.g. folder details page and sidebar tree).

ComposablePurpose
useAppDataQueriesDashboard data, tiles, banners; workspace-scoped.
useAssetsQueriesAsset details; cache by assetId and workspaceId.
useCollectionQueriesCollections list and details; cache by workspaceId and collectionId.
useFolderQueriesRoot folders list; cache by workspaceId.
useProfileQueriesUser profile (e.g. auth user).
useSharingQueriesSharing list; cache by params.

Example:

typescript
const { dashboardQuery } = useAppDataQueries(workspaceId, instanceId)
// dashboardQuery.data, dashboardQuery.isLoading, dashboardQuery.refetch