diff --git a/src/hooks/useEffectState.tsx b/src/hooks/useEffectState.tsx index 75d47bc..9643a70 100644 --- a/src/hooks/useEffectState.tsx +++ b/src/hooks/useEffectState.tsx @@ -15,15 +15,28 @@ type NotifyEffectUpdate = (callback: UpdateCallbackFunc) => void; export function useBatcher() { // Updater Trigger const updateFuncRef = React.useRef(null); + const destroyedRef = React.useRef(false); + + React.useEffect(() => { + destroyedRef.current = false; + return () => { + destroyedRef.current = true; + updateFuncRef.current = null; + }; + }, []); // Notify update const notifyEffectUpdate: NotifyEffectUpdate = callback => { + if (destroyedRef.current) { + return; + } + if (!updateFuncRef.current) { updateFuncRef.current = []; channelUpdate(() => { unstable_batchedUpdates(() => { - updateFuncRef.current.forEach(fn => { + updateFuncRef.current?.forEach(fn => { fn(); }); updateFuncRef.current = null; diff --git a/tests/useEffectState.spec.tsx b/tests/useEffectState.spec.tsx new file mode 100644 index 0000000..dc2f1c9 --- /dev/null +++ b/tests/useEffectState.spec.tsx @@ -0,0 +1,39 @@ +import { act, renderHook } from '@testing-library/react'; +import { useBatcher } from '../src/hooks/useEffectState'; + +describe('useEffectState / useBatcher', () => { + it('should process updates after StrictMode re-establishes effects', async () => { + const callback = jest.fn(); + const { result } = renderHook(() => useBatcher(), { + reactStrictMode: true, + }); + + result.current(callback); + + await act(async () => { + await new Promise(resolve => setTimeout(resolve, 50)); + }); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should clean up queued updates on unmount', async () => { + let notify: any; + let updateCalled = false; + const { unmount } = renderHook(() => { + notify = useBatcher(); + }); + + notify(() => { + updateCalled = true; + }); + + unmount(); + + await act(async () => { + await new Promise(resolve => setTimeout(resolve, 50)); + }); + + expect(updateCalled).toBe(false); + }); +});