mirror of
https://github.com/coleam00/Archon.git
synced 2025-12-30 21:49:30 -05:00
fix: TanStack Query improvements from CodeRabbit review
- Fixed concurrent project creation bug by tracking specific temp IDs - Unified task counts query keys to fix cache invalidation - Added TypeScript generics to getQueryData calls for type safety - Added return type to useTaskCounts hook - Prevented double refetch with refetchOnWindowFocus: false - Improved cache cleanup with exact: false on removeQueries 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,5 +15,6 @@ export {
|
||||
useDeleteProject,
|
||||
useProjectFeatures,
|
||||
useProjects,
|
||||
useTaskCounts,
|
||||
useUpdateProject,
|
||||
} from "./useProjectQueries";
|
||||
|
||||
@@ -25,13 +25,14 @@ export function useProjects() {
|
||||
queryKey: projectKeys.lists(),
|
||||
queryFn: () => projectService.listProjects(),
|
||||
refetchInterval, // Smart interval based on page visibility/focus
|
||||
refetchOnWindowFocus: false, // Avoid double refetch with polling
|
||||
staleTime: 15000, // Consider data stale after 15 seconds
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch task counts for all projects
|
||||
export function useTaskCounts() {
|
||||
return useQuery({
|
||||
return useQuery<Awaited<ReturnType<typeof taskService.getTaskCountsForAllProjects>>>({
|
||||
queryKey: projectKeys.taskCounts(),
|
||||
queryFn: () => taskService.getTaskCountsForAllProjects(),
|
||||
refetchInterval: false, // Don't poll, only refetch manually
|
||||
@@ -61,11 +62,12 @@ export function useCreateProject() {
|
||||
await queryClient.cancelQueries({ queryKey: projectKeys.lists() });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousProjects = queryClient.getQueryData(projectKeys.lists());
|
||||
const previousProjects = queryClient.getQueryData<Project[]>(projectKeys.lists());
|
||||
|
||||
// Create optimistic project with temporary ID
|
||||
const tempId = `temp-${Date.now()}`;
|
||||
const optimisticProject: Project = {
|
||||
id: `temp-${Date.now()}`, // Temporary ID until real one comes back
|
||||
id: tempId, // Temporary ID until real one comes back
|
||||
title: newProjectData.title,
|
||||
description: newProjectData.description,
|
||||
github_repo: newProjectData.github_repo,
|
||||
@@ -85,7 +87,7 @@ export function useCreateProject() {
|
||||
return [optimisticProject, ...old];
|
||||
});
|
||||
|
||||
return { previousProjects };
|
||||
return { previousProjects, tempId };
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
@@ -98,16 +100,16 @@ export function useCreateProject() {
|
||||
|
||||
showToast(`Failed to create project: ${errorMessage}`, "error");
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
onSuccess: (response, _variables, context) => {
|
||||
// Extract the actual project from the response
|
||||
const newProject = response.project;
|
||||
|
||||
// Replace optimistic project with real one from server
|
||||
queryClient.setQueryData(projectKeys.lists(), (old: Project[] | undefined) => {
|
||||
if (!old) return [newProject];
|
||||
// Replace temp project with real one
|
||||
// Replace only the specific temp project with real one
|
||||
return old
|
||||
.map((project) => (project.id.startsWith("temp-") ? newProject : project))
|
||||
.map((project) => (project.id === context?.tempId ? newProject : project))
|
||||
.filter(
|
||||
(project, index, self) =>
|
||||
// Remove any duplicates just in case
|
||||
@@ -137,7 +139,7 @@ export function useUpdateProject() {
|
||||
await queryClient.cancelQueries({ queryKey: projectKeys.lists() });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousProjects = queryClient.getQueryData(projectKeys.lists());
|
||||
const previousProjects = queryClient.getQueryData<Project[]>(projectKeys.lists());
|
||||
|
||||
// Optimistically update
|
||||
queryClient.setQueryData(projectKeys.lists(), (old: Project[] | undefined) => {
|
||||
@@ -189,7 +191,7 @@ export function useDeleteProject() {
|
||||
await queryClient.cancelQueries({ queryKey: projectKeys.lists() });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousProjects = queryClient.getQueryData(projectKeys.lists());
|
||||
const previousProjects = queryClient.getQueryData<Project[]>(projectKeys.lists());
|
||||
|
||||
// Optimistically remove the project
|
||||
queryClient.setQueryData(projectKeys.lists(), (old: Project[] | undefined) => {
|
||||
@@ -212,8 +214,8 @@ export function useDeleteProject() {
|
||||
},
|
||||
onSuccess: (_, projectId) => {
|
||||
// Don't refetch on success - trust optimistic update
|
||||
// Only remove the specific project's detail data
|
||||
queryClient.removeQueries({ queryKey: projectKeys.detail(projectId) });
|
||||
// Only remove the specific project's detail data (including nested keys)
|
||||
queryClient.removeQueries({ queryKey: projectKeys.detail(projectId), exact: false });
|
||||
showToast("Project deleted successfully", "success");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,6 +15,5 @@ export {
|
||||
useCreateTask,
|
||||
useDeleteTask,
|
||||
useProjectTasks,
|
||||
useTaskCounts,
|
||||
useUpdateTask,
|
||||
} from "./useTaskQueries";
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useSmartPolling } from "../../../ui/hooks";
|
||||
import { useToast } from "../../../ui/hooks/useToast";
|
||||
import { projectKeys } from "../../hooks/useProjectQueries";
|
||||
import { taskService } from "../services";
|
||||
import type { CreateTaskRequest, Task, UpdateTaskRequest } from "../types";
|
||||
|
||||
// Query keys factory for tasks
|
||||
export const taskKeys = {
|
||||
all: (projectId: string) => ["projects", projectId, "tasks"] as const,
|
||||
counts: () => ["taskCounts"] as const,
|
||||
};
|
||||
|
||||
// Fetch tasks for a specific project
|
||||
@@ -81,7 +81,7 @@ export function useCreateTask() {
|
||||
index === self.findIndex((t) => t.id === task.id),
|
||||
);
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: taskKeys.counts() });
|
||||
queryClient.invalidateQueries({ queryKey: projectKeys.taskCounts() });
|
||||
showToast("Task created successfully", "success");
|
||||
},
|
||||
onSettled: (_data, _error, variables) => {
|
||||
@@ -124,12 +124,12 @@ export function useUpdateTask(projectId: string) {
|
||||
showToast(`Failed to update task: ${errorMessage}`, "error");
|
||||
// Refetch on error to ensure consistency
|
||||
queryClient.invalidateQueries({ queryKey: taskKeys.all(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: taskKeys.counts() });
|
||||
queryClient.invalidateQueries({ queryKey: projectKeys.taskCounts() });
|
||||
},
|
||||
onSuccess: (_, { updates }) => {
|
||||
// Only invalidate counts if status changed (which affects counts)
|
||||
if (updates.status) {
|
||||
queryClient.invalidateQueries({ queryKey: taskKeys.counts() });
|
||||
queryClient.invalidateQueries({ queryKey: projectKeys.taskCounts() });
|
||||
// Show toast for significant status changes
|
||||
showToast(`Task moved to ${updates.status}`, "success");
|
||||
}
|
||||
@@ -174,17 +174,7 @@ export function useDeleteTask(projectId: string) {
|
||||
},
|
||||
onSettled: () => {
|
||||
// Always refetch counts after deletion
|
||||
queryClient.invalidateQueries({ queryKey: taskKeys.counts() });
|
||||
queryClient.invalidateQueries({ queryKey: projectKeys.taskCounts() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch task counts for all projects
|
||||
export function useTaskCounts() {
|
||||
return useQuery({
|
||||
queryKey: taskKeys.counts(),
|
||||
queryFn: () => taskService.getTaskCountsForAllProjects(),
|
||||
refetchInterval: false, // Don't poll, only refetch manually
|
||||
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,8 +9,13 @@ import { NewProjectModal } from "../components/NewProjectModal";
|
||||
import { ProjectHeader } from "../components/ProjectHeader";
|
||||
import { ProjectList } from "../components/ProjectList";
|
||||
import { DocsTab } from "../documents/DocsTab";
|
||||
import { projectKeys, useDeleteProject, useProjects, useUpdateProject } from "../hooks/useProjectQueries";
|
||||
import { useTaskCounts } from "../tasks/hooks";
|
||||
import {
|
||||
projectKeys,
|
||||
useDeleteProject,
|
||||
useProjects,
|
||||
useTaskCounts,
|
||||
useUpdateProject,
|
||||
} from "../hooks/useProjectQueries";
|
||||
import { TasksTab } from "../tasks/TasksTab";
|
||||
import type { Project } from "../types";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user