From cd102d3efb157029889deee32c13ed28a6c66296 Mon Sep 17 00:00:00 2001 From: Nik Date: Sun, 6 Oct 2024 19:45:08 +0100 Subject: [PATCH] Fix React Strict mode behaviour --- CHANGELOG.md | 3 +++ package.json | 2 +- src/useDebounce.ts | 23 +++++++++++------------ test/useDebounce.test.tsx | 8 ++++---- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e09c10..0fa7bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 10.0.4 +- Fix behaviour for strictMode react when leading is set to true and trailing is true + ## 10.0.3 - Removed `peerDependency` part from `package.json` as NPM cannot correctly resolve `peerDependency` for beta and rc versions: see https://stackoverflow.com/questions/67934358/npm-including-all-range-of-pre-release-when-defining-peer-dependency for context diff --git a/package.json b/package.json index 9fc08da..1d43191 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "use-debounce", - "version": "10.0.3", + "version": "10.0.4-beta", "description": "Debounce hook for react", "source": "src/index.ts", "main": "dist/index.js", diff --git a/src/useDebounce.ts b/src/useDebounce.ts index fdf139e..4dc2493 100644 --- a/src/useDebounce.ts +++ b/src/useDebounce.ts @@ -1,14 +1,10 @@ -import { useCallback, useRef, useReducer } from 'react'; +import { useCallback, useRef, useState } from 'react'; import useDebouncedCallback, { DebouncedState } from './useDebouncedCallback'; function valueEquality(left: T, right: T): boolean { return left === right; } -function reducer(_: T, action: T) { - return action; -} - export default function useDebounce( value: T, delay: number, @@ -21,9 +17,16 @@ export default function useDebounce( ): [T, DebouncedState<(value: T) => void>] { const eq = (options && options.equalityFn) || valueEquality; - const [state, dispatch] = useReducer(reducer, value); + const activeValue = useRef(value); + const [, forceUpdate] = useState({}); const debounced = useDebouncedCallback( - useCallback((value: T) => dispatch(value), [dispatch]), + useCallback( + (value: T) => { + activeValue.current = value; + forceUpdate({}); + }, + [forceUpdate] + ), delay, options ); @@ -34,9 +37,5 @@ export default function useDebounce( previousValue.current = value; } - if (eq(state as T, value)) { - debounced.cancel(); - } - - return [state as T, debounced]; + return [activeValue.current as T, debounced]; } diff --git a/test/useDebounce.test.tsx b/test/useDebounce.test.tsx index f211891..acc1053 100644 --- a/test/useDebounce.test.tsx +++ b/test/useDebounce.test.tsx @@ -65,7 +65,7 @@ describe('useDebounce', () => { // timeout shouldn't have been called yet after leading call was executed // @ts-ignore - expect(screen.getByRole('test')).toHaveTextContent('Hello again'); + expect(screen.getByRole('test')).toHaveTextContent('Hello world'); act(() => { jest.runAllTimers(); @@ -283,13 +283,13 @@ describe('useDebounce', () => { const tree = render(); - expect(eq).toHaveBeenCalledTimes(2); + expect(eq).toHaveBeenCalledTimes(1); act(() => { tree.rerender(); }); - expect(eq).toHaveBeenCalledTimes(4); + expect(eq).toHaveBeenCalledTimes(2); expect(eq).toHaveBeenCalledWith('Hello', 'Test'); // Since the equality function always returns true, expect the value to stay the same // @ts-ignore @@ -490,7 +490,7 @@ describe('useDebounce', () => { // @ts-ignore expect(screen.getByRole('value')).toHaveTextContent('Hello'); // @ts-ignore - expect(screen.getByRole('pending')).toHaveTextContent('false'); + expect(screen.getByRole('pending')).toHaveTextContent('true'); act(() => { jest.runAllTimers();