From 14a77f0f11134c3f85250fae14a0e182f53450d7 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Wed, 4 Mar 2026 09:43:22 +0800 Subject: [PATCH] Fix stale UI after accept: await invalidations, dismiss toasts - Await all query invalidations in respondMutation/cancelMutation onSuccess so UI has fresh data before mutation promise resolves - Use deterministic toast IDs (connection-request-{id}) for Sonner toasts so they can be dismissed from any accept surface - Dismiss stale connection toasts in respondMutation.onSuccess - Fix handleCancel setting resolved before API call completes Co-Authored-By: Claude Opus 4.6 --- .../connections/ConnectionRequestCard.tsx | 3 +-- .../notifications/NotificationToaster.tsx | 10 +++++--- frontend/src/hooks/useConnections.ts | 25 ++++++++++++------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/connections/ConnectionRequestCard.tsx b/frontend/src/components/connections/ConnectionRequestCard.tsx index d4bebdf..7ac76a8 100644 --- a/frontend/src/components/connections/ConnectionRequestCard.tsx +++ b/frontend/src/components/connections/ConnectionRequestCard.tsx @@ -38,12 +38,11 @@ export default function ConnectionRequestCard({ request, direction }: Connection }; const handleCancel = async () => { - setResolved(true); try { await cancelRequest(request.id); + setResolved(true); toast.success('Request cancelled'); } catch (err) { - setResolved(false); toast.error(getErrorMessage(err, 'Failed to cancel request')); } }; diff --git a/frontend/src/components/notifications/NotificationToaster.tsx b/frontend/src/components/notifications/NotificationToaster.tsx index 3e02d51..22e52dd 100644 --- a/frontend/src/components/notifications/NotificationToaster.tsx +++ b/frontend/src/components/notifications/NotificationToaster.tsx @@ -19,9 +19,11 @@ export default function NotificationToaster() { await api.put(`/connections/requests/${requestId}/respond`, { action }); toast.dismiss(toastId); toast.success(action === 'accept' ? 'Connection accepted' : 'Request declined'); - queryClient.invalidateQueries({ queryKey: ['connections'] }); - queryClient.invalidateQueries({ queryKey: ['people'] }); - queryClient.invalidateQueries({ queryKey: ['notifications'] }); + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ['connections'] }), + queryClient.invalidateQueries({ queryKey: ['people'] }), + queryClient.invalidateQueries({ queryKey: ['notifications'] }), + ]); } catch (err) { toast.dismiss(toastId); toast.error(getErrorMessage(err, 'Failed to respond to request')); @@ -109,7 +111,7 @@ export default function NotificationToaster() { ), - { duration: 30000 }, + { id: `connection-request-${requestId}`, duration: 30000 }, ); }; diff --git a/frontend/src/hooks/useConnections.ts b/frontend/src/hooks/useConnections.ts index 61ea79e..65e31b5 100644 --- a/frontend/src/hooks/useConnections.ts +++ b/frontend/src/hooks/useConnections.ts @@ -1,4 +1,5 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner'; import api from '@/lib/api'; import type { Connection, ConnectionRequest, UmbralSearchResponse } from '@/types'; @@ -46,8 +47,8 @@ export function useConnections() { }); return data; }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['connections'] }); + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ['connections'] }); }, }); @@ -56,10 +57,14 @@ export function useConnections() { const { data } = await api.put(`/connections/requests/${requestId}/respond`, { action }); return data; }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['connections'] }); - queryClient.invalidateQueries({ queryKey: ['people'] }); - queryClient.invalidateQueries({ queryKey: ['notifications'] }); + onSuccess: async (_, variables) => { + // Dismiss any lingering Sonner toast for this request + toast.dismiss(`connection-request-${variables.requestId}`); + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ['connections'] }), + queryClient.invalidateQueries({ queryKey: ['people'] }), + queryClient.invalidateQueries({ queryKey: ['notifications'] }), + ]); }, }); @@ -68,9 +73,11 @@ export function useConnections() { const { data } = await api.put(`/connections/requests/${requestId}/cancel`); return data; }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['connections'] }); - queryClient.invalidateQueries({ queryKey: ['notifications'] }); + onSuccess: async () => { + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ['connections'] }), + queryClient.invalidateQueries({ queryKey: ['notifications'] }), + ]); }, });