diff --git a/frontend/src/hooks/useRepeaterDashboard.ts b/frontend/src/hooks/useRepeaterDashboard.ts index 6bae0ef..f94e7c2 100644 --- a/frontend/src/hooks/useRepeaterDashboard.ts +++ b/frontend/src/hooks/useRepeaterDashboard.ts @@ -121,6 +121,7 @@ export function useRepeaterDashboard( // Guard against setting state after unmount (retry timers firing late) const mountedRef = useRef(true); useEffect(() => { + mountedRef.current = true; return () => { mountedRef.current = false; }; diff --git a/frontend/src/test/useRepeaterDashboard.test.ts b/frontend/src/test/useRepeaterDashboard.test.ts index 7ec4836..671ddeb 100644 --- a/frontend/src/test/useRepeaterDashboard.test.ts +++ b/frontend/src/test/useRepeaterDashboard.test.ts @@ -1,3 +1,4 @@ +import { StrictMode, createElement, type ReactNode } from 'react'; import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import { renderHook, act } from '@testing-library/react'; import { useRepeaterDashboard } from '../hooks/useRepeaterDashboard'; @@ -124,6 +125,23 @@ describe('useRepeaterDashboard', () => { expect(result.current.paneStates.status.error).toBe(null); }); + it('refreshPane still issues requests under StrictMode remount probing', async () => { + const statusData = { battery_volts: 4.2 }; + mockApi.repeaterStatus.mockResolvedValueOnce(statusData); + + const wrapper = ({ children }: { children: ReactNode }) => + createElement(StrictMode, null, children); + + const { result } = renderHook(() => useRepeaterDashboard(repeaterConversation), { wrapper }); + + await act(async () => { + await result.current.refreshPane('status'); + }); + + expect(mockApi.repeaterStatus).toHaveBeenCalledTimes(1); + expect(result.current.paneData.status).toEqual(statusData); + }); + it('refreshPane retries up to 3 times', async () => { mockApi.repeaterStatus.mockRejectedValueOnce(new Error('fail1')); mockApi.repeaterStatus.mockRejectedValueOnce(new Error('fail2'));