M-029 Pass 1 batch 5: migrate 2 four-mutation pages to useTrackedMutation

Drains 8 more useMutation sites (18 -> 10). NetworkScanPage hoists the

shared invalidation array into scanTargetInvalidates const.

Pages migrated:

  - IssuersPage.tsx        test/delete/create/update all invalidate [['issuers']]

                            (testIssuerConnection updates last_tested_at

                             server-side, so the list refreshes; local

                             setTestResult banner still surfaces immediate result)

                            (queryClient + useQueryClient dropped)

  - NetworkScanPage.tsx    create/delete/toggle/scan all invalidate

                            [['network-scan-targets']] (hoisted to shared const)

                            (queryClient + useQueryClient dropped)

Verification:

  legacy useMutation count   18 -> 10 (-8)

  useTrackedMutation count   38 -> 46 (+8)

Closes 46 of 56 sites toward M-029 Pass 1 completion (82%).
This commit is contained in:
shankar0123
2026-04-27 02:50:42 +00:00
parent 9e877d2fde
commit 190a27e824
2 changed files with 27 additions and 19 deletions
+14 -9
View File
@@ -1,6 +1,7 @@
import { useEffect, useState, useMemo } from 'react'; import { useEffect, useState, useMemo } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTrackedMutation } from '../hooks/useTrackedMutation';
import { getIssuers, testIssuerConnection, deleteIssuer, createIssuer, updateIssuer } from '../api/client'; import { getIssuers, testIssuerConnection, deleteIssuer, createIssuer, updateIssuer } from '../api/client';
import PageHeader from '../components/PageHeader'; import PageHeader from '../components/PageHeader';
import DataTable from '../components/DataTable'; import DataTable from '../components/DataTable';
@@ -30,7 +31,6 @@ function issuerStatus(issuer: Issuer): string {
} }
export default function IssuersPage() { export default function IssuersPage() {
const queryClient = useQueryClient();
const [testResult, setTestResult] = useState<{ id: string; ok: boolean; msg: string } | null>(null); const [testResult, setTestResult] = useState<{ id: string; ok: boolean; msg: string } | null>(null);
const [showCreateModal, setShowCreateModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false);
const [preselectedType, setPreselectedType] = useState<string | null>(null); const [preselectedType, setPreselectedType] = useState<string | null>(null);
@@ -54,22 +54,27 @@ export default function IssuersPage() {
queryFn: () => getIssuers(), queryFn: () => getIssuers(),
}); });
const testMutation = useMutation({ // testIssuerConnection updates last_tested_at and test_status server-side;
// invalidating ['issuers'] refetches the list so the timestamp + status
// columns reflect the new probe state. The local setTestResult banner
// still surfaces the immediate pass/fail to the operator.
const testMutation = useTrackedMutation({
mutationFn: testIssuerConnection, mutationFn: testIssuerConnection,
invalidates: [['issuers']],
onSuccess: (_data, id) => setTestResult({ id, ok: true, msg: 'Connection successful' }), onSuccess: (_data, id) => setTestResult({ id, ok: true, msg: 'Connection successful' }),
onError: (err: Error, id) => setTestResult({ id, ok: false, msg: err.message }), onError: (err: Error, id) => setTestResult({ id, ok: false, msg: err.message }),
}); });
const deleteMutation = useMutation({ const deleteMutation = useTrackedMutation({
mutationFn: deleteIssuer, mutationFn: deleteIssuer,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['issuers'] }), invalidates: [['issuers']],
}); });
const createMutation = useMutation({ const createMutation = useTrackedMutation({
mutationFn: (data: { name: string; type: string; config: Record<string, unknown> }) => mutationFn: (data: { name: string; type: string; config: Record<string, unknown> }) =>
createIssuer(data), createIssuer(data),
invalidates: [['issuers']],
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['issuers'] });
setShowCreateModal(false); setShowCreateModal(false);
setPreselectedType(null); setPreselectedType(null);
}, },
@@ -80,10 +85,10 @@ export default function IssuersPage() {
// docblock above. Sends `{ name, type, config }` to satisfy the backend // docblock above. Sends `{ name, type, config }` to satisfy the backend
// PUT contract (the handler decodes into a full domain.Issuer struct); // PUT contract (the handler decodes into a full domain.Issuer struct);
// type + config are preserved by reading them from the editing target. // type + config are preserved by reading them from the editing target.
const updateMutation = useMutation({ const updateMutation = useTrackedMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<Issuer> }) => updateIssuer(id, data), mutationFn: ({ id, data }: { id: string; data: Partial<Issuer> }) => updateIssuer(id, data),
invalidates: [['issuers']],
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['issuers'] });
setEditingIssuer(null); setEditingIssuer(null);
}, },
}); });
+13 -10
View File
@@ -1,5 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTrackedMutation } from '../hooks/useTrackedMutation';
import { import {
getNetworkScanTargets, getNetworkScanTargets,
createNetworkScanTarget, createNetworkScanTarget,
@@ -119,7 +120,6 @@ function CreateScanTargetModal({ onClose, onCreate }: {
export default function NetworkScanPage() { export default function NetworkScanPage() {
const [showCreate, setShowCreate] = useState(false); const [showCreate, setShowCreate] = useState(false);
const queryClient = useQueryClient();
const { data, isLoading, error, refetch } = useQuery({ const { data, isLoading, error, refetch } = useQuery({
queryKey: ['network-scan-targets'], queryKey: ['network-scan-targets'],
@@ -127,28 +127,31 @@ export default function NetworkScanPage() {
refetchInterval: 30000, refetchInterval: 30000,
}); });
const createMutation = useMutation({ // Every network-scan-target mutation invalidates the same list query.
const scanTargetInvalidates = [['network-scan-targets']];
const createMutation = useTrackedMutation({
mutationFn: createNetworkScanTarget, mutationFn: createNetworkScanTarget,
invalidates: scanTargetInvalidates,
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['network-scan-targets'] });
setShowCreate(false); setShowCreate(false);
}, },
}); });
const deleteMutation = useMutation({ const deleteMutation = useTrackedMutation({
mutationFn: deleteNetworkScanTarget, mutationFn: deleteNetworkScanTarget,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['network-scan-targets'] }), invalidates: scanTargetInvalidates,
}); });
const toggleMutation = useMutation({ const toggleMutation = useTrackedMutation({
mutationFn: ({ id, enabled }: { id: string; enabled: boolean }) => mutationFn: ({ id, enabled }: { id: string; enabled: boolean }) =>
updateNetworkScanTarget(id, { enabled }), updateNetworkScanTarget(id, { enabled }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['network-scan-targets'] }), invalidates: scanTargetInvalidates,
}); });
const scanMutation = useMutation({ const scanMutation = useTrackedMutation({
mutationFn: triggerNetworkScan, mutationFn: triggerNetworkScan,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['network-scan-targets'] }), invalidates: scanTargetInvalidates,
}); });
const columns: Column<NetworkScanTarget>[] = [ const columns: Column<NetworkScanTarget>[] = [