use-canvases.ts3.5 KBView on GitHub /**
* useCanvases — loads canvases from the backend into canvasSlice.
*
* Implements the data-loading side of §8.1 (CanvasSliceState population).
* Fetches all HomeView-pinned canvases for the current user and seeds them into
* the Zustand canvasSlice (canvasesById + homeViewCanvasIds).
*/
import { useQuery } from '@tanstack/react-query';
import { useSession } from '@/modules/auth/utils/auth-client';
import { useTRPC } from '@/providers/query-provider';
import { useCedarStore } from '@/modules/store';
import { useEffect } from 'react';
import type { Canvas } from '@/modules/canvas/types/canvas-types';
/**
* Fetch all HomeView-pinned canvases visible to the current user and
* sync them into the canvasSlice store.
*
* Returns the raw query result (isLoading, error, etc.) for consumers
* that need to react to loading state.
*/
export function useCanvases() {
const { data: session } = useSession();
const trpc = useTRPC();
const upsertCanvas = useCedarStore((state) => state.upsertCanvas);
const setHomeViewCanvasIds = useCedarStore((state) => state.setHomeViewCanvasIds);
const query = useQuery(
trpc.canvas.getCanvases.queryOptions(
{ homeViewOnly: true },
{
enabled: !!session?.user.id,
staleTime: 30_000, // 30s — HomeView tabs rarely change mid-session
},
),
);
// Sync fetched canvases into the Zustand canvasSlice
useEffect(() => {
if (!query.data) return;
const rows = query.data as Array<{
id: string;
type: string;
title: string;
description: string | null;
action_text: string | null;
data: Canvas['data'];
view_config: Canvas['viewConfig'];
primary_owner: string;
org_id: string | null;
home_view_order: number | null;
colour: string | null;
icon: string | null;
created_at: Date;
updated_at: Date;
effective_home_view_order: number | null;
in_homeview: boolean;
}>;
// Map raw DB rows → Canvas objects
const canvases: Canvas[] = rows.map((row) => ({
id: row.id,
type: row.type as Canvas['type'],
title: row.title,
description: row.description ?? undefined,
actionText: row.action_text ?? undefined,
data: row.data ?? {},
viewConfig: row.view_config,
primaryOwner: row.primary_owner,
orgId: row.org_id ?? undefined,
homeViewOrder: row.home_view_order ?? null,
colour: row.colour ?? undefined,
icon: row.icon ?? undefined,
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at),
updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : String(row.updated_at),
}));
// Seed the store
canvases.forEach((canvas) => upsertCanvas(canvas));
// Set ordered HomeView tab IDs (sorted by effective_home_view_order)
const homeViewRows = rows
.filter((r) => r.in_homeview)
.sort((a, b) => (a.effective_home_view_order ?? 0) - (b.effective_home_view_order ?? 0));
setHomeViewCanvasIds(homeViewRows.map((r) => r.id));
}, [query.data, upsertCanvas, setHomeViewCanvasIds]);
// React Query sets isLoading = isPending && isFetching.
// When the query is disabled (session not yet ready), isPending=true but isFetching=false,
// so isLoading=false — which would incorrectly signal "no canvases" to HomeView.
// Using isPending directly means: "we have no data yet" regardless of fetch status.
// This correctly blocks the auto-open-new-tab logic until real data is available.
return { ...query, isLoading: query.isPending };
}