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 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-04 09:43:22 +08:00
parent b554ba7151
commit 14a77f0f11
3 changed files with 23 additions and 15 deletions

View File

@ -38,12 +38,11 @@ export default function ConnectionRequestCard({ request, direction }: Connection
}; };
const handleCancel = async () => { const handleCancel = async () => {
setResolved(true);
try { try {
await cancelRequest(request.id); await cancelRequest(request.id);
setResolved(true);
toast.success('Request cancelled'); toast.success('Request cancelled');
} catch (err) { } catch (err) {
setResolved(false);
toast.error(getErrorMessage(err, 'Failed to cancel request')); toast.error(getErrorMessage(err, 'Failed to cancel request'));
} }
}; };

View File

@ -19,9 +19,11 @@ export default function NotificationToaster() {
await api.put(`/connections/requests/${requestId}/respond`, { action }); await api.put(`/connections/requests/${requestId}/respond`, { action });
toast.dismiss(toastId); toast.dismiss(toastId);
toast.success(action === 'accept' ? 'Connection accepted' : 'Request declined'); toast.success(action === 'accept' ? 'Connection accepted' : 'Request declined');
queryClient.invalidateQueries({ queryKey: ['connections'] }); await Promise.all([
queryClient.invalidateQueries({ queryKey: ['people'] }); queryClient.invalidateQueries({ queryKey: ['connections'] }),
queryClient.invalidateQueries({ queryKey: ['notifications'] }); queryClient.invalidateQueries({ queryKey: ['people'] }),
queryClient.invalidateQueries({ queryKey: ['notifications'] }),
]);
} catch (err) { } catch (err) {
toast.dismiss(toastId); toast.dismiss(toastId);
toast.error(getErrorMessage(err, 'Failed to respond to request')); toast.error(getErrorMessage(err, 'Failed to respond to request'));
@ -109,7 +111,7 @@ export default function NotificationToaster() {
</div> </div>
</div> </div>
), ),
{ duration: 30000 }, { id: `connection-request-${requestId}`, duration: 30000 },
); );
}; };

View File

@ -1,4 +1,5 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import api from '@/lib/api'; import api from '@/lib/api';
import type { Connection, ConnectionRequest, UmbralSearchResponse } from '@/types'; import type { Connection, ConnectionRequest, UmbralSearchResponse } from '@/types';
@ -46,8 +47,8 @@ export function useConnections() {
}); });
return data; return data;
}, },
onSuccess: () => { onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: ['connections'] }); await queryClient.invalidateQueries({ queryKey: ['connections'] });
}, },
}); });
@ -56,10 +57,14 @@ export function useConnections() {
const { data } = await api.put(`/connections/requests/${requestId}/respond`, { action }); const { data } = await api.put(`/connections/requests/${requestId}/respond`, { action });
return data; return data;
}, },
onSuccess: () => { onSuccess: async (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['connections'] }); // Dismiss any lingering Sonner toast for this request
queryClient.invalidateQueries({ queryKey: ['people'] }); toast.dismiss(`connection-request-${variables.requestId}`);
queryClient.invalidateQueries({ queryKey: ['notifications'] }); 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`); const { data } = await api.put(`/connections/requests/${requestId}/cancel`);
return data; return data;
}, },
onSuccess: () => { onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: ['connections'] }); await Promise.all([
queryClient.invalidateQueries({ queryKey: ['notifications'] }); queryClient.invalidateQueries({ queryKey: ['connections'] }),
queryClient.invalidateQueries({ queryKey: ['notifications'] }),
]);
}, },
}); });