-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new hooks
useThrottledEffect
and useThrottledState
- Loading branch information
Showing
13 changed files
with
298 additions
and
5 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
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,33 @@ | ||
import * as React from 'react'; | ||
import { useState } from 'react'; | ||
import { useThrottledEffect } from '../..'; | ||
|
||
const HAS_DIGIT_REGEX = /[\d]/g; | ||
|
||
export const Example: React.FC = () => { | ||
const [state, setState] = useState(''); | ||
const [hasNumbers, setHasNumbers] = useState(false); | ||
|
||
useThrottledEffect( | ||
() => { | ||
setHasNumbers(HAS_DIGIT_REGEX.test(state)); | ||
}, | ||
[state], | ||
200 | ||
); | ||
|
||
return ( | ||
<div> | ||
<div>Digit check will be performed no more than one every 200ms</div> | ||
<br /> | ||
<div>{hasNumbers ? 'Input has digits' : 'No digits found in input'}</div> | ||
<input | ||
type="text" | ||
value={state} | ||
onChange={(ev) => { | ||
setState(ev.target.value); | ||
}} | ||
/> | ||
</div> | ||
); | ||
}; |
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,35 @@ | ||
import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks'; | ||
import { Example } from './example.stories'; | ||
|
||
<Meta title="New Hook/useThrottledEffect" component={Example} /> | ||
|
||
# useThrottledEffect | ||
|
||
Like `useEffect`, but passed function is throttled. | ||
|
||
#### Example | ||
|
||
<Canvas> | ||
<Story story={Example} inline /> | ||
</Canvas> | ||
|
||
## Reference | ||
|
||
```ts | ||
export function useThrottledEffect( | ||
callback: (...args: any[]) => void, | ||
deps: DependencyList, | ||
delay: number, | ||
noTrailing = false | ||
): void; | ||
``` | ||
|
||
#### Arguments | ||
|
||
- **callback** _`(...args: any[]) => void`_ - Callback like for `useEffect`, but without ability to | ||
return a cleanup function. | ||
- **deps** _`DependencyList`_ - Dependencies list that will be passed to underlying `useEffect` and | ||
`useThrottledCallback`. | ||
- **delay** _`number`_ - throttle delay. | ||
- **noTrailing** _`boolean`_ _(default: false)_ - if noTrailing is true, callback will only execute | ||
every `delay` milliseconds, otherwise, callback will be executed once, after the last call. |
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 { renderHook } from '@testing-library/react-hooks/dom'; | ||
import { useThrottledEffect } from '../..'; | ||
|
||
describe('useThrottledEffect', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(useThrottledEffect).toBeDefined(); | ||
}); | ||
|
||
it('should render', () => { | ||
const { result } = renderHook(() => useThrottledEffect(() => {}, [], 200)); | ||
expect(result.error).toBeUndefined(); | ||
}); | ||
|
||
it('should throttle passed callback', () => { | ||
const spy = jest.fn(); | ||
const { rerender } = renderHook((dep) => useThrottledEffect(spy, [dep], 200, true), { | ||
initialProps: 1, | ||
}); | ||
|
||
expect(spy).toHaveBeenCalledTimes(1); | ||
rerender(2); | ||
rerender(3); | ||
rerender(4); | ||
expect(spy).toHaveBeenCalledTimes(1); | ||
|
||
jest.advanceTimersByTime(200); | ||
expect(spy).toHaveBeenCalledTimes(1); | ||
rerender(5); | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
}); | ||
}); |
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,21 @@ | ||
import { renderHook } from '@testing-library/react-hooks/server'; | ||
import { useThrottledEffect } from '../..'; | ||
|
||
describe('useThrottledEffect', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(useThrottledEffect).toBeDefined(); | ||
}); | ||
|
||
it('should render', () => { | ||
const { result } = renderHook(() => useThrottledEffect(() => {}, [], 200)); | ||
expect(result.error).toBeUndefined(); | ||
}); | ||
}); |
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,25 @@ | ||
import { DependencyList, useEffect } from 'react'; | ||
import { useThrottledCallback } from '..'; | ||
|
||
/** | ||
* Like `useEffect`, but passed function is throttled. | ||
* | ||
* @param callback Callback like for `useEffect`, but without ability to return | ||
* a cleanup function. | ||
* @param deps Dependencies list that will be passed to underlying `useEffect` | ||
* and `useThrottledCallback`. | ||
* @param delay Throttle delay. | ||
* @param noTrailing If noTrailing is true, callback will only execute every | ||
* `delay` milliseconds, otherwise, callback will be executed one final time | ||
* after the last throttled-function call. | ||
*/ | ||
export function useThrottledEffect( | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
callback: (...args: any[]) => void, | ||
deps: DependencyList, | ||
delay: number, | ||
noTrailing = false | ||
): void { | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
useEffect(useThrottledCallback(callback, deps, delay, noTrailing), deps); | ||
} |
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,15 @@ | ||
import React from 'react'; | ||
import { useThrottledState } from '../..'; | ||
|
||
export const Example: React.FC = () => { | ||
const [state, setState] = useThrottledState('', 500); | ||
|
||
return ( | ||
<div> | ||
<div>Below state will update no more than once every 500ms</div> | ||
<br /> | ||
<div>The input`s value is: {state}</div> | ||
<input type="text" onChange={(ev) => setState(ev.target.value)} /> | ||
</div> | ||
); | ||
}; |
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,37 @@ | ||
import {Canvas, Meta, Story} from '@storybook/addon-docs/blocks'; | ||
import {Example} from './example.stories'; | ||
|
||
<Meta title="State/useThrottledState" component={Example} /> | ||
|
||
# useThrottledState | ||
|
||
Lise `useSafeState` but its state setter is throttled. | ||
|
||
#### Example | ||
|
||
<Canvas> | ||
<Story story={Example} inline /> | ||
</Canvas> | ||
|
||
## Reference | ||
|
||
```ts | ||
export function useThrottledState<S>( | ||
initialState: S | (() => S), | ||
delay: number, | ||
noTrailing = false | ||
): [S, Dispatch<SetStateAction<S>>]; | ||
``` | ||
|
||
#### Arguments | ||
|
||
- **initialState** _`S | (() => S)`_ - Initial state to pass to underlying `useSafeState`. | ||
- **delay** _`number`_ - Throttle delay. | ||
- **noTrailing** _`boolean`_ _(default: false)_ - if noTrailing is true, callback will only execute | ||
every `delay` milliseconds, otherwise, callback will be executed once, after the last call. | ||
|
||
#### Return | ||
|
||
0. **state** - current state. | ||
1. **setState** - throttled state setter. | ||
|
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,40 @@ | ||
import { renderHook, act } from '@testing-library/react-hooks/dom'; | ||
import { useThrottledState } from '../..'; | ||
|
||
describe('useThrottledState', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(useThrottledState).toBeDefined(); | ||
}); | ||
|
||
it('should render', () => { | ||
const { result } = renderHook(() => useThrottledState('', 200)); | ||
expect(result.error).toBeUndefined(); | ||
}); | ||
|
||
it('should throttle set state', () => { | ||
const { result } = renderHook(() => useThrottledState('', 200, true)); | ||
|
||
expect(result.current[0]).toBe(''); | ||
act(() => { | ||
result.current[1]('hello world!'); | ||
}); | ||
expect(result.current[0]).toBe('hello world!'); | ||
|
||
result.current[1]('foo'); | ||
result.current[1]('bar'); | ||
expect(result.current[0]).toBe('hello world!'); | ||
jest.advanceTimersByTime(200); | ||
act(() => { | ||
result.current[1]('baz'); | ||
}); | ||
expect(result.current[0]).toBe('baz'); | ||
}); | ||
}); |
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,21 @@ | ||
import { renderHook } from '@testing-library/react-hooks/server'; | ||
import { useThrottledState } from '../..'; | ||
|
||
describe('useThrottledState', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(useThrottledState).toBeDefined(); | ||
}); | ||
|
||
it('should render', () => { | ||
const { result } = renderHook(() => useThrottledState('', 200)); | ||
expect(result.error).toBeUndefined(); | ||
}); | ||
}); |
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,21 @@ | ||
import { Dispatch, SetStateAction } from 'react'; | ||
import { useSafeState, useThrottledCallback } from '..'; | ||
|
||
/** | ||
* Like `useSafeState` but its state setter is throttled. | ||
* | ||
* @param initialState Initial state to pass to underlying `useSafeState`. | ||
* @param delay Throttle delay. | ||
* @param noTrailing If noTrailing is true, callback will only execute every | ||
* `delay` milliseconds, otherwise, callback will be executed one final time | ||
* after the last throttled-function call. | ||
*/ | ||
export function useThrottledState<S>( | ||
initialState: S | (() => S), | ||
delay: number, | ||
noTrailing = false | ||
): [S, Dispatch<SetStateAction<S>>] { | ||
const [state, setState] = useSafeState(initialState); | ||
|
||
return [state, useThrottledCallback(setState, [], delay, noTrailing)]; | ||
} |