export { dashboardKeys } from "./keys";
export { dashboardMappers } from "./mappers";
export { dashboardMocks } from "./mocks";
export { dashboardService } from "./services";
export { dashboardQueries } from "./queries";
export { dashboardMutations } from "./mutations";
export type * from "./types";
export const dashboardKeys = {
all: () => ["dashboard"] as const,
stats: () => [...dashboardKeys.all(), "stats"] as const,
revenue: () => [...dashboardKeys.all(), "revenue"] as const,
alerts: () => [...dashboardKeys.all(), "alerts"] as const,
};
import { mutationOptions, type QueryClient } from "@tanstack/react-query";
import { toast } from "@/components/base";
import { dashboardKeys } from "./keys";
import { dashboardService } from "./services";
import type { CreateAlertInput } from "./types";
export const dashboardMutations = {
createAlert: (queryClient: QueryClient) =>
mutationOptions({
mutationFn: (input: CreateAlertInput) => dashboardService.createAlert(input),
onMutate: () => {
toast.info({
id: "dashboard_create_alert_submitting",
title: "Submitting…",
duration: 0,
});
},
onSuccess: async () => {
toast.dismiss("dashboard_create_alert_submitting");
await queryClient.invalidateQueries({ queryKey: dashboardKeys.alerts() });
toast.success({ title: "Submitted" });
},
onError: (error) => {
toast.dismiss("dashboard_create_alert_submitting");
toast.error({
title: "Submission failed",
description: error instanceof Error ? error.message : undefined,
});
},
}),
};
import { queryOptions } from "@tanstack/react-query";
import { dashboardKeys } from "./keys";
import { dashboardMappers } from "./mappers";
import { dashboardMocks } from "./mocks";
import { dashboardService } from "./services";
export const dashboardQueries = {
stats: () =>
queryOptions({
queryKey: dashboardKeys.stats(),
queryFn: async () => {
try {
const raw = await dashboardService.getStats();
return dashboardMappers.statCards(raw as any);
} catch {
return dashboardMappers.statCards(dashboardMocks.stats());
}
},
}),
revenue: () =>
queryOptions({
queryKey: dashboardKeys.revenue(),
queryFn: async () => {
try {
return await dashboardService.getRevenue();
} catch {
return dashboardMocks.revenue();
}
},
}),
alerts: () =>
queryOptions({
queryKey: dashboardKeys.alerts(),
queryFn: async () => {
try {
const raw = await dashboardService.getAlerts();
return dashboardMappers.alerts(raw as any);
} catch {
return dashboardMappers.alerts(dashboardMocks.alerts());
}
},
}),
};
import { apiClient } from "@/services/http/client";
export const dashboardService = {
getStats: () => apiClient.get("/dashboard/stats"),
getRevenue: () => apiClient.get("/dashboard/revenue"),
getAlerts: () => apiClient.get("/dashboard/alerts"),
createAlert: (input: any) => apiClient.post("/dashboard/alerts", input),
};
README
# Dashboard Service (TanStack Query)
This folder contains everything needed to fetch, transform, cache, and mutate “Dashboard” data using TanStack Query v5 in a **feature-based** layout (similar to RTK Query).
## How the files connect
**High-level data flow**
1. **Component** calls a predefined query/mutation option
2. **queries.ts / mutations.ts** run a `queryFn` / `mutationFn`
3. **services.ts** performs the HTTP request (API calls only)
4. **mappers.ts** transforms raw API data into UI-ready objects (icons, colors, hrefs, etc.)
5. **mocks.ts** provides fallback data when the API fails (dev-friendly)
6. **keys.ts** defines consistent query keys for caching + invalidation
## File responsibilities
### `keys.ts`
- Centralized query keys for the dashboard namespace.
- Used by queries and mutations to:
- cache correctly (`queryKey`)
- invalidate correctly (`invalidateQueries`)
Example:
- `dashboardKeys.alerts()` → `["dashboard", "alerts"]`
### `services.ts`
- **API calls only** (no UI logic, no mapping).
- Uses the shared HTTP client (`src/services/http/client.ts`) to call endpoints like:
- `GET /dashboard/stats`
- `GET /dashboard/revenue`
- `GET /dashboard/alerts`
- `POST /dashboard/alerts`
### `mappers.ts`
- **UI transformation only**.
- Converts raw API responses into what components render:
- icon selection (Lucide)
- `href` mapping (router `PATHS`)
- color class names
- any formatting/shape changes
### `mocks.ts`
- **Fallback/mock responses only**.
- Used inside `try/catch` blocks in `queries.ts` / `mutations.ts`.
- Keeps components stable even if the backend isn’t running.
### `queries.ts`
- Exports **centralized** TanStack Query `queryOptions()` builders.
- Each query:
- uses `dashboardKeys.*()` for `queryKey`
- calls `dashboardService.*()` for API data
- applies `dashboardMappers.*()` for UI-ready data
- falls back to `dashboardMocks.*()` when the API fails
### `mutations.ts`
- Exports **centralized** TanStack Query `mutationOptions()` builders.
- Mutations handle **automatic invalidation** so components don’t need data-logic.
- Example: after `createAlert` succeeds → invalidate `dashboardKeys.alerts()`
### `types.ts`
- Shared TypeScript types for:
- raw API payloads (`Raw*`)
- UI-ready types (`StatCardData`, `AlertData`)
- mutation inputs (e.g. `CreateAlertInput`)
### `index.ts`
- Single “barrel” export for the whole feature.
- Makes imports clean and consistent.
## Typical usage in a page/component
ts
import { useSuspenseQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { dashboardQueries, dashboardMutations } from "@/services/dashboard";
const alerts = useSuspenseQuery(dashboardQueries.alerts());
const queryClient = useQueryClient();
const createAlert = useMutation(dashboardMutations.createAlert(queryClient));
## Adding a new endpoint (checklist)
1. Add raw types (if needed) in `types.ts`
2. Add API call in `services.ts`
3. Add mock response in `mocks.ts` (optional but recommended during dev)
4. Add mapper in `mappers.ts` if UI transformation is needed
5. Add key in `keys.ts` (especially if it needs invalidation)
6. Add `queryOptions()` in `queries.ts` **or** `mutationOptions()` in `mutations.ts`
7. Export from `index.ts`

Top comments (0)