From ba17bf6b632739344a9c1ea2afe69c19af00b31a Mon Sep 17 00:00:00 2001 From: dannyy2000 Date: Sat, 27 Jun 2026 21:04:51 +0100 Subject: [PATCH] fix(frontend): stabilize transactionId with useId to prevent progress UI loss and double submission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1105 Date.now() was called on every render inside useDepositOperation, useWithdrawalOperation, and useRepaymentOperation, minting a new transactionId each time. Because useTransaction subscribes to the Zustand store, every store write (start/sign/submit/confirm) triggered a re-render which produced a new id. The executeDeposit/executeWithdrawal closure kept writing to the original id while the component read from the new one — so transaction was undefined, OperationProgress showed nothing, and isLoading stayed false, allowing duplicate submissions. Fix: replace Date.now() with React's useId() so the id is stable across all renders for the lifetime of the hook instance. Also extend isLoading in useTransaction to cover the submitted and confirming states so the Deposit/Withdraw buttons remain disabled for the entire in-flight window, not just pending/signing. --- frontend/src/app/hooks/useOptimisticUI.ts | 6 +++++- frontend/src/app/hooks/useRepaymentOperation.ts | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/hooks/useOptimisticUI.ts b/frontend/src/app/hooks/useOptimisticUI.ts index 5264645c..1cf74429 100644 --- a/frontend/src/app/hooks/useOptimisticUI.ts +++ b/frontend/src/app/hooks/useOptimisticUI.ts @@ -236,7 +236,11 @@ export function useTransaction(id: string) { sign: (message?: string) => store.signTransaction(id, message), fail: (error: string) => store.failTransaction(id, error), clear: () => store.clearTransaction(id), - isLoading: transaction?.status === "pending" || transaction?.status === "signing", + isLoading: + transaction?.status === "pending" || + transaction?.status === "signing" || + transaction?.status === "submitted" || + transaction?.status === "confirming", isSigning: transaction?.status === "signing", isSubmitted: transaction?.status === "submitted", isConfirming: transaction?.status === "confirming", diff --git a/frontend/src/app/hooks/useRepaymentOperation.ts b/frontend/src/app/hooks/useRepaymentOperation.ts index 56a16f33..06a40060 100644 --- a/frontend/src/app/hooks/useRepaymentOperation.ts +++ b/frontend/src/app/hooks/useRepaymentOperation.ts @@ -20,7 +20,7 @@ * ``` */ -import { useCallback, useState } from "react"; +import { useCallback, useId, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { useTransaction } from "./useOptimisticUI"; import { useWallet } from "../components/providers/WalletProvider"; @@ -47,7 +47,8 @@ export function useRepaymentOperation(options?: { onError?: (error: Error) => void; }) { const queryClient = useQueryClient(); - const transactionId = `repayment-${Date.now()}`; + const uid = useId(); + const transactionId = `repayment-${uid}`; const transaction = useTransaction(transactionId); const [error, setError] = useState(null); @@ -126,7 +127,8 @@ export function useDepositOperation(options?: { const buildDeposit = useDepositToPool(); const { data: poolStats } = usePoolStats(); - const transactionId = `deposit-${Date.now()}`; + const uid = useId(); + const transactionId = `deposit-${uid}`; const transaction = useTransaction(transactionId); const [error, setError] = useState(null); @@ -217,7 +219,8 @@ export function useWithdrawalOperation(options?: { const buildWithdraw = useWithdrawFromPool(); const { data: poolStats } = usePoolStats(); - const transactionId = `withdrawal-${Date.now()}`; + const uid = useId(); + const transactionId = `withdrawal-${uid}`; const transaction = useTransaction(transactionId); const [error, setError] = useState(null);