Skip to content

Commit

Permalink
feat: add useChanging for change detection
Browse files Browse the repository at this point in the history
close #113
  • Loading branch information
kripod committed Oct 26, 2019
1 parent 47e4758 commit b5a95d1
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 0 deletions.
22 changes: 22 additions & 0 deletions packages/state-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,33 @@ import { usePrevious, useUndoable } from 'state-hooks';

#### Table of Contents

- [useChanging](#usechanging)
- [usePrevious](#useprevious)
- [useTimeline](#usetimeline)
- [useToggle](#usetoggle)
- [useUndoable](#useundoable)

### useChanging

Tracks whether a value has changed over a relatively given period of time.

#### Parameters

- `value` **T** Props, state or any other calculated value.
- `groupingIntervalMs` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** Time interval, in milliseconds, to group a batch of changes by. (optional, default `150`)

#### Examples

```javascript
function Component() {
const scrollCoords = useWindowScrollCoords();
const isScrolling = useChanging(scrollCoords);
// ...
}
```

Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** `true` if the value has changed at least once over the given interval, or `false` otherwise.

### usePrevious

Tracks previous state of a value.
Expand Down
1 change: 1 addition & 0 deletions packages/state-hooks/documentation.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
toc:
- useChanging
- usePrevious
- useTimeline
- useToggle
Expand Down
1 change: 1 addition & 0 deletions packages/state-hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as useChanging } from './useChanging';
export { default as usePrevious } from './usePrevious';
export { default as useTimeline } from './useTimeline';
export { default as useToggle } from './useToggle';
Expand Down
51 changes: 51 additions & 0 deletions packages/state-hooks/src/useChanging.test.ts
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);
});
39 changes: 39 additions & 0 deletions packages/state-hooks/src/useChanging.ts
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;
}

0 comments on commit b5a95d1

Please sign in to comment.