Skip to content

Commit 4081406

Browse files
committed
reactUtils: Add useHasNotChangedForMs, factored from useHasStayedTrueForMs
We'll use this new, more generic Hook useHasNotChangedForMs later in this series. Keep useHasStayedTrueForMs, but have it call this new Hook. For that change to its implementation, it's helpful that useHasStayedTrueForMs has pretty thorough test coverage.
1 parent 4eafc24 commit 4081406

File tree

2 files changed

+42
-9
lines changed

2 files changed

+42
-9
lines changed

src/__tests__/reactUtils-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import { create, act } from 'react-test-renderer';
77
import { fakeSleep } from './lib/fakeTimers';
88
import { useHasStayedTrueForMs } from '../reactUtils';
99

10+
describe('useHasNotChangedForMs', () => {
11+
// This gets indirect coverage because useHasStayedTrueForMs calls it, and
12+
// that hook has good coverage, below. But:
13+
// TODO: Test directly.
14+
});
15+
1016
describe('useHasStayedTrueForMs', () => {
1117
/**
1218
* Simulate a mock component using the hook, and inspect hook's value.

src/reactUtils.js

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ export function useDebugAssertConstant<T>(value: T) {
5353
}
5454

5555
/**
56-
* True just when `value` has been true continuously for the past `duration`.
56+
* True just when `value` has not changed for the past `duration`.
57+
*
58+
* "Changed" means last render's and this render's `value`s aren't ===.
5759
*
5860
* When the given time has elapsed so that this hook's return value becomes
5961
* true, it causes a rerender through a state update.
@@ -66,28 +68,53 @@ export function useDebugAssertConstant<T>(value: T) {
6668
* that in particular this hook gets called again) whenever `value` will
6769
* have changed; for example, by using a prop or a `useState` value.
6870
*/
69-
export const useHasStayedTrueForMs = (value: boolean, duration: number): boolean => {
71+
export const useHasNotChangedForMs = (value: mixed, duration: number): boolean => {
7072
useDebugAssertConstant(duration);
7173

7274
const [result, setResult] = useState(false);
7375

7476
useEffect(() => {
75-
if (value) {
76-
const id = setTimeout(() => setResult(true), duration);
77-
return () => clearTimeout(id);
78-
} else {
79-
setResult(false);
80-
}
77+
setResult(false);
78+
const id = setTimeout(() => setResult(true), duration);
79+
return () => clearTimeout(id);
80+
}, [
8181
// If `duration` changes, we'll tear down the old timeout and start the
8282
// timer over. That isn't really ideal behavior... but we don't
8383
// actually have a use case for a dynamic `duration`, and supporting it
8484
// properly would be more complex, so we've just forbidden that as part
8585
// of this hook function's interface.
86-
}, [value, duration]);
86+
duration,
87+
88+
// Otherwise, trigger the effect just if React sees a change in `value`.
89+
// In other words, just when last render's and this render's `value`s
90+
// aren't ===.
91+
value,
92+
]);
8793

8894
return result;
8995
};
9096

97+
/**
98+
* True just when `value` has been true continuously for the past `duration`.
99+
*
100+
* When the given time has elapsed so that this hook's return value becomes
101+
* true, it causes a rerender through a state update.
102+
*
103+
* The caller must use a constant `duration` through the lifetime of a given
104+
* component instance.
105+
*
106+
* Note this hook doesn't (and can't) do anything to cause a rerender when
107+
* `value` changes. The caller must ensure that the component rerenders (so
108+
* that in particular this hook gets called again) whenever `value` will
109+
* have changed; for example, by using a prop or a `useState` value.
110+
*/
111+
export const useHasStayedTrueForMs = (value: boolean, duration: number): boolean => {
112+
useDebugAssertConstant(duration);
113+
114+
const hasNotChangedForDuration = useHasNotChangedForMs(value, duration);
115+
return value && hasNotChangedForDuration;
116+
};
117+
91118
/**
92119
* Like `useEffect`, but the callback only runs when `value` is true.
93120
*

0 commit comments

Comments
 (0)