From 319439ac2286e145f2c6f6409c16be347428bf81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 8 May 2026 15:40:04 +0200 Subject: [PATCH] fix(medications): convert API-endpoint dialog status fetch to useQuery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dialog's "load endpoint status" effect was triggering a react-hooks/set-state-in-effect lint error: setLoadingStatus(true) fired synchronously inside an effect, which the rule flags as a cascading-render risk. Convert the imperative useCallback + useEffect pair to a single useQuery — same fetch shape, but loading/data state now lives in TanStack Query and the effect goes away. The "Reload status" menu item calls refetch(); the toggle path uses queryClient.setQueryData to keep the displayed state in sync after a PUT, so users still see the new active-token count without a second roundtrip. Errors from the GET render through the same callout that toggle errors do. No user-visible change. Restores lint to 0 errors on main. Co-Authored-By: Marc-André Bombeck --- src/app/medications/page.tsx | 97 +++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/src/app/medications/page.tsx b/src/app/medications/page.tsx index e45a3bf9..830ef681 100644 --- a/src/app/medications/page.tsx +++ b/src/app/medications/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; @@ -485,20 +485,52 @@ function ApiEndpointDialog({ onClose: () => void; }) { const { t } = useTranslations(); + const queryClient = useQueryClient(); type ExampleType = "curl" | "wget" | "fetch" | "powershell"; - const [enabled, setEnabled] = useState(false); - const [activeTokenCount, setActiveTokenCount] = useState(0); - const [loadingStatus, setLoadingStatus] = useState(false); + const apiEndpointKey = ["medication-api-endpoint", medication?.id]; + + type ApiEndpointStatus = { enabled: boolean; activeTokenCount: number }; + + const { + data: status, + isFetching: loadingStatus, + refetch: refetchStatus, + error: statusError, + } = useQuery({ + queryKey: apiEndpointKey, + queryFn: async () => { + const res = await fetch( + `/api/medications/${medication!.id}/api-endpoint`, + ); + const json = await res.json(); + if (!res.ok) { + throw new Error(json.error || t("medications.statusLoadFailed")); + } + return { + enabled: json.data.enabled === true, + activeTokenCount: json.data.activeTokenCount ?? 0, + }; + }, + enabled: !!medication, + staleTime: 0, + gcTime: 0, + }); + + const enabled = status?.enabled ?? false; + const activeTokenCount = status?.activeTokenCount ?? 0; + const [token, setToken] = useState(null); const [toggling, setToggling] = useState(false); const [msg, setMsg] = useState(null); const [copied, setCopied] = useState(null); const [exampleType, setExampleType] = useState("curl"); + const displayMsg = + msg ?? (statusError instanceof Error ? statusError.message : null); + function handleClose() { - setEnabled(false); - setActiveTokenCount(0); + queryClient.removeQueries({ queryKey: apiEndpointKey }); setToken(null); setMsg(null); setCopied(null); @@ -506,27 +538,6 @@ function ApiEndpointDialog({ onClose(); } - const loadStatus = useCallback(async () => { - if (!medication) return; - setLoadingStatus(true); - setMsg(null); - try { - const res = await fetch(`/api/medications/${medication.id}/api-endpoint`); - if (res.ok) { - const json = await res.json(); - setEnabled(json.data.enabled === true); - setActiveTokenCount(json.data.activeTokenCount ?? 0); - } else { - const json = await res.json(); - setMsg(json.error || t("medications.statusLoadFailed")); - } - } catch { - setMsg(t("medications.statusLoadFailed")); - } finally { - setLoadingStatus(false); - } - }, [medication, t]); - async function toggleEndpoint(nextEnabled: boolean) { if (!medication) return; setToggling(true); @@ -547,15 +558,16 @@ function ApiEndpointDialog({ return; } - setEnabled(json.data.enabled === true); - if (typeof json.data.activeTokenCount === "number") { - setActiveTokenCount(json.data.activeTokenCount); - } else if ( - !json.data.enabled && - typeof json.data.revokedTokenCount === "number" - ) { - setActiveTokenCount(0); - } + const nextStatus: ApiEndpointStatus = { + enabled: json.data.enabled === true, + activeTokenCount: + typeof json.data.activeTokenCount === "number" + ? json.data.activeTokenCount + : !json.data.enabled + ? 0 + : (status?.activeTokenCount ?? 0), + }; + queryClient.setQueryData(apiEndpointKey, nextStatus); if (json.data.token) { setToken(json.data.token); @@ -579,11 +591,6 @@ function ApiEndpointDialog({ setTimeout(() => setCopied(null), 2000); } - useEffect(() => { - if (!medication) return; - void loadStatus(); - }, [medication, loadStatus]); - const baseUrl = typeof window !== "undefined" ? window.location.origin : "https://..."; const endpoint = `${baseUrl}/api/ingest/medication`; @@ -756,11 +763,11 @@ function ApiEndpointDialog({ )} - {msg && ( + {displayMsg && (

- {msg} + {displayMsg}

)} @@ -780,7 +787,7 @@ function ApiEndpointDialog({ { - void loadStatus(); + void refetchStatus(); }} disabled={loadingStatus || toggling} >