-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add useChanging for change detection
close #113
- Loading branch information
Showing
5 changed files
with
114 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
toc: | ||
- useChanging | ||
- usePrevious | ||
- useTimeline | ||
- useToggle | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { act, renderHook } from '@testing-library/react-hooks'; | ||
import { useChanging } from '.'; | ||
|
||
jest.useFakeTimers(); | ||
|
||
test('detect changes of a value over time', () => { | ||
const { result, rerender } = renderHook(({ value }) => useChanging(value), { | ||
initialProps: { value: 0 }, | ||
}); | ||
expect(result.current).toBe(false); | ||
|
||
rerender({ value: 1 }); | ||
expect(result.current).toBe(true); | ||
|
||
act(() => { | ||
jest.advanceTimersByTime(100); | ||
}); | ||
expect(result.current).toBe(true); | ||
|
||
act(() => { | ||
jest.advanceTimersByTime(100); | ||
}); | ||
expect(result.current).toBe(false); | ||
|
||
rerender({ value: 2 }); | ||
expect(result.current).toBe(true); | ||
}); | ||
|
||
test('handle changing grouping interval', () => { | ||
const { result, rerender } = renderHook( | ||
({ value, groupingIntervalMs }) => useChanging(value, groupingIntervalMs), | ||
{ initialProps: { value: 0, groupingIntervalMs: 500 } }, | ||
); | ||
|
||
rerender({ value: 1, groupingIntervalMs: 500 }); | ||
act(() => { | ||
jest.advanceTimersByTime(300); | ||
}); | ||
expect(result.current).toBe(true); | ||
|
||
rerender({ value: 1, groupingIntervalMs: 1000 }); | ||
act(() => { | ||
jest.advanceTimersByTime(700); | ||
}); | ||
expect(result.current).toBe(true); | ||
|
||
act(() => { | ||
jest.advanceTimersByTime(300); | ||
}); | ||
expect(result.current).toBe(false); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { useEffect, useRef, useState } from 'react'; | ||
|
||
/** | ||
* Tracks whether a value has changed over a relatively given period of time. | ||
* | ||
* @param value Props, state or any other calculated value. | ||
* @param {number} groupingIntervalMs Time interval, in milliseconds, to group a batch of changes by. | ||
* @returns `true` if the value has changed at least once over the given interval, or `false` otherwise. | ||
* | ||
* @example | ||
* function Component() { | ||
* const scrollCoords = useWindowScrollCoords(); | ||
* const isScrolling = useChanging(scrollCoords); | ||
* // ... | ||
* } | ||
*/ | ||
export default function useChanging<T>( | ||
value: T, | ||
groupingIntervalMs = 150, | ||
): boolean { | ||
const [isChanging, setChanging] = useState(false); | ||
const prevGroupingIntervalMsRef = useRef(0); | ||
|
||
useEffect(() => { | ||
// Prevent initial state from being true | ||
if (groupingIntervalMs !== prevGroupingIntervalMsRef.current) { | ||
prevGroupingIntervalMsRef.current = groupingIntervalMs; | ||
} else { | ||
setChanging(true); | ||
} | ||
|
||
const timeoutID = setTimeout(() => setChanging(false), groupingIntervalMs); | ||
return () => { | ||
clearTimeout(timeoutID); | ||
}; | ||
}, [groupingIntervalMs, value]); | ||
|
||
return isChanging; | ||
} |