From 4bdb86057e348b67f13e35eda07904bb7e963aeb Mon Sep 17 00:00:00 2001 From: Bohan Jiang <52446949+Bohan-J@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:10:40 +0800 Subject: [PATCH] fix(issues): use TanStack Query for sub-issue data fetching (#499) The sub-issue code was using direct `api` calls, but the codebase was refactored to TanStack Query and the `api` import was removed from issue-detail.tsx, causing a build error on Vercel. Replace useState+useEffect with useQuery for both parent and child issue fetching, consistent with the TQ migration. --- apps/web/core/issues/queries.ts | 9 ++++ .../issues/components/issue-detail.tsx | 44 +++++-------------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/apps/web/core/issues/queries.ts b/apps/web/core/issues/queries.ts index ec358933..a8c265ed 100644 --- a/apps/web/core/issues/queries.ts +++ b/apps/web/core/issues/queries.ts @@ -6,6 +6,8 @@ export const issueKeys = { list: (wsId: string) => [...issueKeys.all(wsId), "list"] as const, detail: (wsId: string, id: string) => [...issueKeys.all(wsId), "detail", id] as const, + children: (wsId: string, id: string) => + [...issueKeys.all(wsId), "children", id] as const, timeline: (issueId: string) => ["issues", "timeline", issueId] as const, reactions: (issueId: string) => ["issues", "reactions", issueId] as const, subscribers: (issueId: string) => @@ -47,6 +49,13 @@ export function issueDetailOptions(wsId: string, id: string) { }); } +export function childIssuesOptions(wsId: string, id: string) { + return queryOptions({ + queryKey: issueKeys.children(wsId, id), + queryFn: () => api.listChildIssues(id).then((r) => r.issues), + }); +} + export function issueTimelineOptions(issueId: string) { return queryOptions({ queryKey: issueKeys.timeline(issueId), diff --git a/apps/web/features/issues/components/issue-detail.tsx b/apps/web/features/issues/components/issue-detail.tsx index 97becdef..aa23e790 100644 --- a/apps/web/features/issues/components/issue-detail.tsx +++ b/apps/web/features/issues/components/issue-detail.tsx @@ -68,7 +68,7 @@ import { useQuery } from "@tanstack/react-query"; import { useAuthStore } from "@/features/auth"; import { useWorkspaceStore, useActorName } from "@/features/workspace"; import { useWorkspaceId } from "@core/hooks"; -import { issueListOptions, issueDetailOptions } from "@core/issues/queries"; +import { issueListOptions, issueDetailOptions, childIssuesOptions } from "@core/issues/queries"; import { memberListOptions, agentListOptions } from "@core/workspace/queries"; import { useUpdateIssue, useDeleteIssue } from "@core/issues/mutations"; import { useIssueTimeline } from "@/features/issues/hooks/use-issue-timeline"; @@ -227,37 +227,17 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo subscribers, loading: subscribersLoading, isSubscribed, toggleSubscribe: handleToggleSubscribe, toggleSubscriber, } = useIssueSubscribers(id, user?.id); - // Sub-issue state — derive from store when possible, fetch otherwise - const [parentIssue, setParentIssue] = useState(null); - const [childIssues, setChildIssues] = useState([]); - - // Fetch parent issue when parent_issue_id changes - useEffect(() => { - if (!issue?.parent_issue_id) { - setParentIssue(null); - return; - } - // Try store first, then fetch - const storeParent = allIssues.find((i) => i.id === issue.parent_issue_id); - if (storeParent) { - setParentIssue(storeParent); - } else { - api.getIssue(issue.parent_issue_id).then(setParentIssue).catch(() => setParentIssue(null)); - } - }, [issue?.parent_issue_id, allIssues]); - - // Fetch child issues once, then keep in sync via store - const childIssuesFromStore = allIssues.filter((i) => i.parent_issue_id === id); - useEffect(() => { - if (!issue) return; - // If store has children, use them directly - if (childIssuesFromStore.length > 0) { - setChildIssues(childIssuesFromStore); - return; - } - // Fetch from API (children may not be in the store yet, e.g. deep-linked) - api.listChildIssues(issue.id).then((r) => setChildIssues(r.issues)).catch(() => setChildIssues([])); - }, [issue?.id, childIssuesFromStore.length]); + // Sub-issue queries + const parentIssueId = issue?.parent_issue_id; + const { data: parentIssue = null } = useQuery({ + ...issueDetailOptions(wsId, parentIssueId ?? ""), + enabled: !!parentIssueId, + initialData: () => allIssues.find((i) => i.id === parentIssueId), + }); + const { data: childIssues = [] } = useQuery({ + ...childIssuesOptions(wsId, id), + enabled: !!issue, + }); const loading = issueLoading;