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.
This commit is contained in:
Bohan Jiang 2026-04-08 16:10:40 +08:00 committed by GitHub
parent a8a8ff6eca
commit 4bdb86057e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 21 additions and 32 deletions

View file

@ -6,6 +6,8 @@ export const issueKeys = {
list: (wsId: string) => [...issueKeys.all(wsId), "list"] as const, list: (wsId: string) => [...issueKeys.all(wsId), "list"] as const,
detail: (wsId: string, id: string) => detail: (wsId: string, id: string) =>
[...issueKeys.all(wsId), "detail", id] as const, [...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, timeline: (issueId: string) => ["issues", "timeline", issueId] as const,
reactions: (issueId: string) => ["issues", "reactions", issueId] as const, reactions: (issueId: string) => ["issues", "reactions", issueId] as const,
subscribers: (issueId: string) => 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) { export function issueTimelineOptions(issueId: string) {
return queryOptions({ return queryOptions({
queryKey: issueKeys.timeline(issueId), queryKey: issueKeys.timeline(issueId),

View file

@ -68,7 +68,7 @@ import { useQuery } from "@tanstack/react-query";
import { useAuthStore } from "@/features/auth"; import { useAuthStore } from "@/features/auth";
import { useWorkspaceStore, useActorName } from "@/features/workspace"; import { useWorkspaceStore, useActorName } from "@/features/workspace";
import { useWorkspaceId } from "@core/hooks"; 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 { memberListOptions, agentListOptions } from "@core/workspace/queries";
import { useUpdateIssue, useDeleteIssue } from "@core/issues/mutations"; import { useUpdateIssue, useDeleteIssue } from "@core/issues/mutations";
import { useIssueTimeline } from "@/features/issues/hooks/use-issue-timeline"; 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, subscribers, loading: subscribersLoading, isSubscribed, toggleSubscribe: handleToggleSubscribe, toggleSubscriber,
} = useIssueSubscribers(id, user?.id); } = useIssueSubscribers(id, user?.id);
// Sub-issue state — derive from store when possible, fetch otherwise // Sub-issue queries
const [parentIssue, setParentIssue] = useState<Issue | null>(null); const parentIssueId = issue?.parent_issue_id;
const [childIssues, setChildIssues] = useState<Issue[]>([]); const { data: parentIssue = null } = useQuery({
...issueDetailOptions(wsId, parentIssueId ?? ""),
// Fetch parent issue when parent_issue_id changes enabled: !!parentIssueId,
useEffect(() => { initialData: () => allIssues.find((i) => i.id === parentIssueId),
if (!issue?.parent_issue_id) { });
setParentIssue(null); const { data: childIssues = [] } = useQuery({
return; ...childIssuesOptions(wsId, id),
} enabled: !!issue,
// 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]);
const loading = issueLoading; const loading = issueLoading;