diff --git a/.changeset/lovely-spies-change.md b/.changeset/lovely-spies-change.md new file mode 100644 index 00000000..cd1a354c --- /dev/null +++ b/.changeset/lovely-spies-change.md @@ -0,0 +1,5 @@ +--- +'react-simplikit': patch +--- + +fix(core/hooks): prevent immediate callback from re-firing when enabled is toggled diff --git a/packages/core/src/hooks/useInterval/useInterval.spec.ts b/packages/core/src/hooks/useInterval/useInterval.spec.ts index 9d58ff27..e989766a 100644 --- a/packages/core/src/hooks/useInterval/useInterval.spec.ts +++ b/packages/core/src/hooks/useInterval/useInterval.spec.ts @@ -124,6 +124,26 @@ describe('useInterval', () => { }); }); + it('should not re-fire immediate callback when enabled is toggled', async () => { + const callback = vi.fn(); + const { rerender } = await renderHookSSR( + ({ enabled }) => + useInterval(callback, { + delay: 1000, + immediate: true, + enabled, + }), + { initialProps: { enabled: true } } + ); + + expect(callback).toHaveBeenCalledTimes(1); + + rerender({ enabled: false }); + rerender({ enabled: true }); + + expect(callback).toHaveBeenCalledTimes(1); + }); + it('should handle enabled flag changes appropriately', async () => { const callback = vi.fn(); const { rerender } = await renderHookSSR( diff --git a/packages/core/src/hooks/useInterval/useInterval.ts b/packages/core/src/hooks/useInterval/useInterval.ts index 996dbaf0..e47871b1 100644 --- a/packages/core/src/hooks/useInterval/useInterval.ts +++ b/packages/core/src/hooks/useInterval/useInterval.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { usePreservedCallback } from '../usePreservedCallback/index.ts'; @@ -45,12 +45,23 @@ export function useInterval(callback: () => void, options: IntervalOptions) { const enabled = typeof options === 'number' ? true : (options.enabled ?? true); const preservedCallback = usePreservedCallback(callback); + const immediateCalledRef = useRef(false); - useEffect(() => { - if (immediate === true && enabled) { + useEffect( + function runImmediateCallback() { + if (immediate !== true || !enabled) { + return; + } + + if (immediateCalledRef.current) { + return; + } + + immediateCalledRef.current = true; preservedCallback(); - } - }, [immediate, preservedCallback, enabled]); + }, + [immediate, preservedCallback, enabled] + ); useEffect(() => { if (!enabled) {