From 34ac9df7434634f0da03ad4ade4aa3c15c34e7be Mon Sep 17 00:00:00 2001 From: Will Chen Date: Thu, 8 May 2025 22:04:20 -0700 Subject: [PATCH] Refactor useVersions to @tanstack/react-query (#114) working --- package-lock.json | 27 +++++++ package.json | 1 + src/components/chat/VersionPane.tsx | 23 +++--- src/hooks/useVersions.ts | 111 ++++++++++++++-------------- src/renderer.tsx | 11 ++- 5 files changed, 105 insertions(+), 68 deletions(-) diff --git a/package-lock.json b/package-lock.json index f701d96..6b66ab6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "@rollup/plugin-commonjs": "^28.0.3", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.3", + "@tanstack/react-query": "^5.75.5", "@tanstack/react-router": "^1.114.34", "@types/uuid": "^10.0.0", "@vitejs/plugin-react": "^4.3.4", @@ -5361,6 +5362,32 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/query-core": { + "version": "5.75.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.75.5.tgz", + "integrity": "sha512-kPDOxtoMn2Ycycb76Givx2fi+2pzo98F9ifHL/NFiahEDpDwSVW6o12PRuQ0lQnBOunhRG5etatAhQij91M3MQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.75.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.75.5.tgz", + "integrity": "sha512-QrLCJe40BgBVlWdAdf2ZEVJ0cISOuEy/HKupId1aTKU6gPJZVhSvZpH+Si7csRflCJphzlQ77Yx6gUxGW9o0XQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.75.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tanstack/react-router": { "version": "1.119.0", "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.119.0.tgz", diff --git a/package.json b/package.json index fa4668b..55d4290 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@rollup/plugin-commonjs": "^28.0.3", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.3", + "@tanstack/react-query": "^5.75.5", "@tanstack/react-router": "^1.114.34", "@types/uuid": "^10.0.0", "@vitejs/plugin-react": "^4.3.4", diff --git a/src/components/chat/VersionPane.tsx b/src/components/chat/VersionPane.tsx index 0caacfa..81adaa7 100644 --- a/src/components/chat/VersionPane.tsx +++ b/src/components/chat/VersionPane.tsx @@ -21,17 +21,20 @@ export function VersionPane({ isVisible, onClose }: VersionPaneProps) { selectedVersionIdAtom, ); useEffect(() => { - // Refresh versions in case the user updated versions outside of the app - // (e.g. manually using git). - // Avoid loading state which causes brief flash of loading state. - refreshVersions(); - if (!isVisible && selectedVersionId) { - setSelectedVersionId(null); - IpcClient.getInstance().checkoutVersion({ - appId: appId!, - versionId: "main", - }); + async function updateVersions() { + // Refresh versions in case the user updated versions outside of the app + // (e.g. manually using git). + // Avoid loading state which causes brief flash of loading state. + if (!isVisible && selectedVersionId) { + setSelectedVersionId(null); + await IpcClient.getInstance().checkoutVersion({ + appId: appId!, + versionId: "main", + }); + } + refreshVersions(); } + updateVersions(); }, [isVisible, refreshVersions]); if (!isVisible) { return null; diff --git a/src/hooks/useVersions.ts b/src/hooks/useVersions.ts index 43d50c5..785abb4 100644 --- a/src/hooks/useVersions.ts +++ b/src/hooks/useVersions.ts @@ -1,78 +1,79 @@ -import { useState, useEffect, useCallback } from "react"; +import { useCallback, useEffect } from "react"; import { useAtom, useAtomValue } from "jotai"; import { versionsListAtom } from "@/atoms/appAtoms"; import { IpcClient } from "@/ipc/ipc_client"; import { showError } from "@/lib/toast"; import { chatMessagesAtom, selectedChatIdAtom } from "@/atoms/chatAtoms"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import type { Version } from "@/ipc/ipc_types"; export function useVersions(appId: number | null) { - const [versions, setVersions] = useAtom(versionsListAtom); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [, setVersionsAtom] = useAtom(versionsListAtom); const selectedChatId = useAtomValue(selectedChatIdAtom); const [, setMessages] = useAtom(chatMessagesAtom); - useEffect(() => { - const loadVersions = async () => { - // If no app is selected, clear versions and return + const queryClient = useQueryClient(); + + const { + data: versions, + isLoading: loading, + error, + refetch: refreshVersions, + } = useQuery({ + queryKey: ["versions", appId], + queryFn: async (): Promise => { if (appId === null) { - setVersions([]); - setLoading(false); - return; + return []; } - - try { - const ipcClient = IpcClient.getInstance(); - const versionsList = await ipcClient.listVersions({ appId }); - - setVersions(versionsList); - setError(null); - } catch (error) { - console.error("Error loading versions:", error); - setError(error instanceof Error ? error : new Error(String(error))); - } finally { - setLoading(false); - } - }; - - loadVersions(); - }, [appId, setVersions]); - - const refreshVersions = useCallback(async () => { - if (appId === null) { - return; - } - - try { const ipcClient = IpcClient.getInstance(); - const versionsList = await ipcClient.listVersions({ appId }); - setVersions(versionsList); - setError(null); - } catch (error) { - console.error("Error refreshing versions:", error); - setError(error instanceof Error ? error : new Error(String(error))); + return ipcClient.listVersions({ appId }); + }, + enabled: appId !== null, + initialData: [], + }); + + useEffect(() => { + if (versions) { + setVersionsAtom(versions); } - }, [appId, setVersions, setError]); + }, [versions, setVersionsAtom]); + + const revertVersionMutation = useMutation( + { + mutationFn: async ({ versionId }: { versionId: string }) => { + if (appId === null) { + throw new Error("App ID is null"); + } + const ipcClient = IpcClient.getInstance(); + await ipcClient.revertVersion({ appId, previousVersionId: versionId }); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["versions", appId] }); + if (selectedChatId) { + const chat = await IpcClient.getInstance().getChat(selectedChatId); + setMessages(chat.messages); + } + }, + onError: (e: Error) => { + showError(e); + }, + }, + ); const revertVersion = useCallback( async ({ versionId }: { versionId: string }) => { if (appId === null) { return; } - - try { - const ipcClient = IpcClient.getInstance(); - await ipcClient.revertVersion({ appId, previousVersionId: versionId }); - await refreshVersions(); - if (selectedChatId) { - const chat = await IpcClient.getInstance().getChat(selectedChatId); - setMessages(chat.messages); - } - } catch (error) { - showError(error); - } + await revertVersionMutation.mutateAsync({ versionId }); }, - [appId, setVersions, setError, selectedChatId, setMessages], + [appId, revertVersionMutation], ); - return { versions, loading, error, refreshVersions, revertVersion }; + return { + versions: versions || [], + loading, + error, + refreshVersions, + revertVersion, + }; } diff --git a/src/renderer.tsx b/src/renderer.tsx index 537aa3c..d746f3a 100644 --- a/src/renderer.tsx +++ b/src/renderer.tsx @@ -5,10 +5,13 @@ import { RouterProvider } from "@tanstack/react-router"; import { PostHogProvider } from "posthog-js/react"; import posthog from "posthog-js"; import { getTelemetryUserId, isTelemetryOptedIn } from "./hooks/useSettings"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; // @ts-ignore console.log("Running in mode:", import.meta.env.MODE); +const queryClient = new QueryClient(); + const posthogClient = posthog.init( "phc_5Vxx0XT8Ug3eWROhP6mm4D6D2DgIIKT232q4AKxC2ab", { @@ -71,8 +74,10 @@ function App() { createRoot(document.getElementById("root")!).render( - - - + + + + + , );