Skip to content

Commit bc2bb34

Browse files
jonathanlabclaude
andcommitted
refactor: extract useAuthenticatedQuery and useAuthenticatedMutation
Eliminated repetitive auth + query/mutation pattern across hooks by creating: - useAuthenticatedQuery: handles client auth checks for queries - useAuthenticatedMutation: handles client auth checks for mutations Updated hooks: - useUsers: simplified query pattern - useIntegrations: simplified query pattern - useTasks and all task mutations (create/update/delete/duplicate) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 79b4e53 commit bc2bb34

File tree

5 files changed

+150
-100
lines changed

5 files changed

+150
-100
lines changed
Lines changed: 66 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { useAuthStore } from "@features/auth/stores/authStore";
21
import { useTaskExecutionStore } from "@features/tasks/stores/taskExecutionStore";
2+
import { useAuthenticatedMutation } from "@hooks/useAuthenticatedMutation";
3+
import { useAuthenticatedQuery } from "@hooks/useAuthenticatedQuery";
34
import type { Task } from "@shared/types";
4-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5+
import { useQueryClient } from "@tanstack/react-query";
56

67
const taskKeys = {
78
all: ["tasks"] as const,
@@ -16,105 +17,99 @@ export function useTasks(filters?: {
1617
repositoryOrg?: string;
1718
repositoryName?: string;
1819
}) {
19-
const client = useAuthStore((state) => state.client);
20-
21-
return useQuery({
22-
queryKey: taskKeys.list(filters),
23-
queryFn: async () => {
24-
if (!client) throw new Error("Not authenticated");
25-
return (await client.getTasks(
20+
return useAuthenticatedQuery(
21+
taskKeys.list(filters),
22+
(client) =>
23+
client.getTasks(
2624
filters?.repositoryOrg,
2725
filters?.repositoryName,
28-
)) as unknown as Task[];
29-
},
30-
enabled: !!client,
31-
});
26+
) as unknown as Promise<Task[]>,
27+
);
3228
}
3329

3430
export function useCreateTask() {
35-
const client = useAuthStore((state) => state.client);
3631
const queryClient = useQueryClient();
3732

38-
return useMutation({
39-
mutationFn: async ({
40-
description,
41-
repositoryConfig,
42-
}: {
43-
description: string;
44-
repositoryConfig?: { organization: string; repository: string };
45-
}) => {
46-
if (!client) throw new Error("Not authenticated");
47-
const task = (await client.createTask(
33+
return useAuthenticatedMutation(
34+
(
35+
client,
36+
{
4837
description,
4938
repositoryConfig,
50-
)) as unknown as Task;
51-
return task;
52-
},
53-
onSuccess: () => {
54-
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
39+
}: {
40+
description: string;
41+
repositoryConfig?: { organization: string; repository: string };
42+
},
43+
) =>
44+
client.createTask(
45+
description,
46+
repositoryConfig,
47+
) as unknown as Promise<Task>,
48+
{
49+
onSuccess: () => {
50+
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
51+
},
5552
},
56-
});
53+
);
5754
}
5855

5956
export function useUpdateTask() {
60-
const client = useAuthStore((state) => state.client);
6157
const queryClient = useQueryClient();
6258

63-
return useMutation({
64-
mutationFn: async ({
65-
taskId,
66-
updates,
67-
}: {
68-
taskId: string;
69-
updates: Partial<Task>;
70-
}) => {
71-
if (!client) throw new Error("Not authenticated");
72-
return await client.updateTask(
59+
return useAuthenticatedMutation(
60+
(
61+
client,
62+
{
63+
taskId,
64+
updates,
65+
}: {
66+
taskId: string;
67+
updates: Partial<Task>;
68+
},
69+
) =>
70+
client.updateTask(
7371
taskId,
7472
updates as Parameters<typeof client.updateTask>[1],
75-
);
76-
},
77-
onSuccess: (_, { taskId }) => {
78-
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
79-
queryClient.invalidateQueries({ queryKey: taskKeys.detail(taskId) });
73+
),
74+
{
75+
onSuccess: (_, { taskId }) => {
76+
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
77+
queryClient.invalidateQueries({ queryKey: taskKeys.detail(taskId) });
78+
},
8079
},
81-
});
80+
);
8281
}
8382

8483
export function useDeleteTask() {
85-
const client = useAuthStore((state) => state.client);
8684
const queryClient = useQueryClient();
8785

88-
return useMutation({
89-
mutationFn: async (taskId: string) => {
90-
if (!client) throw new Error("Not authenticated");
91-
await client.deleteTask(taskId);
86+
return useAuthenticatedMutation(
87+
(client, taskId: string) => client.deleteTask(taskId),
88+
{
89+
onSuccess: () => {
90+
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
91+
},
9292
},
93-
onSuccess: () => {
94-
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
95-
},
96-
});
93+
);
9794
}
9895

9996
export function useDuplicateTask() {
100-
const client = useAuthStore((state) => state.client);
10197
const queryClient = useQueryClient();
10298

103-
return useMutation({
104-
mutationFn: async (taskId: string) => {
105-
if (!client) throw new Error("Not authenticated");
106-
return (await client.duplicateTask(taskId)) as unknown as Task;
107-
},
108-
onSuccess: (newTask, originalTaskId) => {
109-
// Copy working directory from original task to new task
110-
const { getTaskState, setRepoPath } = useTaskExecutionStore.getState();
111-
const originalState = getTaskState(originalTaskId);
99+
return useAuthenticatedMutation(
100+
(client, taskId: string) =>
101+
client.duplicateTask(taskId) as unknown as Promise<Task>,
102+
{
103+
onSuccess: (newTask, originalTaskId) => {
104+
const { getTaskState, setRepoPath } = useTaskExecutionStore.getState();
105+
const originalState = getTaskState(originalTaskId);
112106

113-
if (originalState.repoPath) {
114-
setRepoPath(newTask.id, originalState.repoPath);
115-
}
107+
if (originalState.repoPath) {
108+
setRepoPath(newTask.id, originalState.repoPath);
109+
}
116110

117-
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
111+
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
112+
},
118113
},
119-
});
114+
);
120115
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { PostHogAPIClient } from "@api/posthogClient";
2+
import { useAuthStore } from "@features/auth/stores/authStore";
3+
import type {
4+
UseMutationOptions,
5+
UseMutationResult,
6+
} from "@tanstack/react-query";
7+
import { useMutation } from "@tanstack/react-query";
8+
9+
type AuthenticatedMutationFn<TVariables, TData> = (
10+
client: PostHogAPIClient,
11+
variables: TVariables,
12+
) => Promise<TData>;
13+
14+
export function useAuthenticatedMutation<
15+
TData = unknown,
16+
TError = Error,
17+
TVariables = void,
18+
>(
19+
mutationFn: AuthenticatedMutationFn<TVariables, TData>,
20+
options?: Omit<UseMutationOptions<TData, TError, TVariables>, "mutationFn">,
21+
): UseMutationResult<TData, TError, TVariables> {
22+
const client = useAuthStore((state) => state.client);
23+
24+
return useMutation({
25+
mutationFn: async (variables: TVariables) => {
26+
if (!client) throw new Error("Not authenticated");
27+
return await mutationFn(client, variables);
28+
},
29+
...options,
30+
});
31+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { PostHogAPIClient } from "@api/posthogClient";
2+
import { useAuthStore } from "@features/auth/stores/authStore";
3+
import type {
4+
QueryKey,
5+
UseQueryOptions,
6+
UseQueryResult,
7+
} from "@tanstack/react-query";
8+
import { useQuery } from "@tanstack/react-query";
9+
10+
type AuthenticatedQueryFn<T> = (client: PostHogAPIClient) => Promise<T>;
11+
12+
export function useAuthenticatedQuery<
13+
TData = unknown,
14+
TError = Error,
15+
TQueryKey extends QueryKey = QueryKey,
16+
>(
17+
queryKey: TQueryKey,
18+
queryFn: AuthenticatedQueryFn<TData>,
19+
options?: Omit<
20+
UseQueryOptions<TData, TError, TData, TQueryKey>,
21+
"queryKey" | "queryFn" | "enabled"
22+
>,
23+
): UseQueryResult<TData, TError> {
24+
const client = useAuthStore((state) => state.client);
25+
26+
return useQuery({
27+
queryKey,
28+
queryFn: async () => {
29+
if (!client) throw new Error("Not authenticated");
30+
return await queryFn(client);
31+
},
32+
enabled: !!client,
33+
...options,
34+
});
35+
}

src/renderer/hooks/useIntegrations.ts

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { useAuthStore } from "@features/auth/stores/authStore";
21
import {
32
useIntegrationSelectors,
43
useIntegrationStore,
54
} from "@features/integrations/stores/integrationStore";
65
import type { RepositoryConfig } from "@shared/types";
7-
import { useQuery } from "@tanstack/react-query";
86
import { useEffect } from "react";
7+
import { useAuthenticatedQuery } from "./useAuthenticatedQuery";
98

109
interface Integration {
1110
id: number;
@@ -21,17 +20,12 @@ const integrationKeys = {
2120
};
2221

2322
export function useIntegrations() {
24-
const client = useAuthStore((state) => state.client);
2523
const setIntegrations = useIntegrationStore((state) => state.setIntegrations);
2624

27-
const query = useQuery({
28-
queryKey: integrationKeys.list(),
29-
queryFn: async () => {
30-
if (!client) throw new Error("Not authenticated");
31-
return (await client.getIntegrations()) as Integration[];
32-
},
33-
enabled: !!client,
34-
});
25+
const query = useAuthenticatedQuery(
26+
integrationKeys.list(),
27+
(client) => client.getIntegrations() as Promise<Integration[]>,
28+
);
3529

3630
useEffect(() => {
3731
if (query.data) {
@@ -43,20 +37,17 @@ export function useIntegrations() {
4337
}
4438

4539
function useRepositories(integrationId?: number) {
46-
const client = useAuthStore((state) => state.client);
4740
const setRepositories = useIntegrationStore((state) => state.setRepositories);
4841

49-
const query = useQuery({
50-
queryKey: integrationKeys.repositories(integrationId),
51-
queryFn: async () => {
52-
if (!client) throw new Error("Not authenticated");
42+
const query = useAuthenticatedQuery(
43+
integrationKeys.repositories(integrationId),
44+
async (client) => {
5345
if (!integrationId) return [];
5446
return (await client.getGithubRepositories(
5547
integrationId,
5648
)) as RepositoryConfig[];
5749
},
58-
enabled: !!client && !!integrationId,
59-
});
50+
);
6051

6152
useEffect(() => {
6253
if (query.data) {

src/renderer/hooks/useUsers.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
import { useAuthStore } from "@features/auth/stores/authStore";
1+
import type { Schemas } from "@api/generated";
22
import { useUsersStore } from "@stores/usersStore";
3-
import { useQuery } from "@tanstack/react-query";
43
import { useEffect } from "react";
4+
import { useAuthenticatedQuery } from "./useAuthenticatedQuery";
55

66
export function useUsers() {
7-
const client = useAuthStore((state) => state.client);
87
const setUsers = useUsersStore((state) => state.setUsers);
98

10-
const query = useQuery({
11-
queryKey: ["users"],
12-
queryFn: async () => {
13-
if (!client) throw new Error("Not authenticated");
14-
return await client.getUsers();
9+
const query = useAuthenticatedQuery(
10+
["users"],
11+
async (client) => {
12+
const data = await client.getUsers();
13+
return data as Schemas.UserBasic[];
1514
},
16-
enabled: !!client,
17-
staleTime: 5 * 60 * 1000, // 5 minutes
18-
});
15+
{ staleTime: 5 * 60 * 1000 },
16+
);
1917

2018
useEffect(() => {
2119
if (query.data) {

0 commit comments

Comments
 (0)