use-debounce
Advanced tools
Comparing version 1.0.1-beta to 1.1.0
@@ -0,1 +1,33 @@ | ||
## 1.1.0 | ||
- add `callPending` callback to `useDebouncedCallback` method. It allows to call the callback manually if it hasn't fired yet. This method is handy to use when the user takes an action that would cause the component to unmount, but you need to execute the callback. | ||
```javascript | ||
import React, { useState, useCallback } from 'react'; | ||
import useDebouncedCallback from 'use-debounce/lib/callback'; | ||
function InputWhichFetchesSomeData({ defaultValue, asyncFetchData }) { | ||
const [debouncedFunction, cancel, callPending] = useDebouncedCallback( | ||
(value) => { | ||
asyncFetchData; | ||
}, | ||
500, | ||
[], | ||
{ maxWait: 2000 } | ||
); | ||
// When the component goes to be unmounted, we will fetch data if the input has changed. | ||
useEffect( | ||
() => () => { | ||
callPending(); | ||
}, | ||
[] | ||
); | ||
return <input defaultValue={defaultValue} onChange={(e) => debouncedFunction(e.target.value)} />; | ||
} | ||
``` | ||
More examples are available here: https://github.com/xnimorz/use-debounce/commit/989d6c0efb4eef080ed78330233186d7b0c249e3#diff-c7e0cfdec8acc174d3301ff43b986264R196 | ||
## 1.0.0 | ||
@@ -15,3 +47,3 @@ | ||
- add cancel callback (thanks to @thibaultboursier for contributing). Cancel callback removes func from the queue (even maxWait): | ||
- add cancel callback (thanks to [@thibaultboursier](https://github.com/thibaultboursier) for contributing). Cancel callback removes func from the queue (even maxWait): | ||
@@ -18,0 +50,0 @@ ```javascript |
{ | ||
"name": "use-debounce", | ||
"version": "1.0.1-beta", | ||
"version": "1.1.0", | ||
"description": "Debounce hook for react", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -32,3 +32,3 @@ # useDebounce react hook | ||
const [text, setText] = useState('Hello'); | ||
const [value, cancelValue] = useDebounce(text, 1000); | ||
const [value] = useDebounce(text, 1000); | ||
@@ -61,3 +61,3 @@ return ( | ||
// Debounce callback | ||
const [debouncedCallback, cancelDebouncedCallback] = useDebouncedCallback( | ||
const [debouncedCallback] = useDebouncedCallback( | ||
// function | ||
@@ -122,1 +122,71 @@ (value) => { | ||
``` | ||
### Advanced usage | ||
#### Cancel and maxWait | ||
1. Both `useDebounce` and `useDebouncedCallback` works with `maxWait` option. This params describes the maximum time func is allowed to be delayed before it's invoked. | ||
2. You can cancel debounce cycle, by calling `cancel` callback | ||
The full example you can see here https://codesandbox.io/s/4wvmp1xlw4 | ||
```javascript | ||
import React, { useState, useCallback } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import useDebouncedCallback from 'use-debounce/lib/callback'; | ||
function Input({ defaultValue }) { | ||
const [value, setValue] = useState(defaultValue); | ||
const [debouncedFunction, cancel] = useDebouncedCallback( | ||
(value) => { | ||
setValue(value); | ||
}, | ||
500, | ||
[], | ||
// The maximum time func is allowed to be delayed before it's invoked: | ||
{ maxWait: 2000 } | ||
); | ||
// you should use `e => debouncedFunction(e.target.value)` as react works with synthetic evens | ||
return ( | ||
<div> | ||
<input defaultValue={defaultValue} onChange={(e) => debouncedFunction(e.target.value)} /> | ||
<p>Debounced value: {value}</p> | ||
<button onClick={cancel}>Cancel Debounce cycle</button> | ||
</div> | ||
); | ||
} | ||
const rootElement = document.getElementById('root'); | ||
ReactDOM.render(<Input defaultValue="Hello world" />, rootElement); | ||
``` | ||
#### callPending method | ||
`useDebouncedCallback` has `callPending` method. It allows to call the callback manually if it hasn't fired yet. This method is handy to use when the user takes an action that would cause the component to unmount, but you need to execute the callback. | ||
```javascript | ||
import React, { useState, useCallback } from 'react'; | ||
import useDebouncedCallback from 'use-debounce/lib/callback'; | ||
function InputWhichFetchesSomeData({ defaultValue, asyncFetchData }) { | ||
const [debouncedFunction, cancel, callPending] = useDebouncedCallback( | ||
(value) => { | ||
asyncFetchData; | ||
}, | ||
500, | ||
[], | ||
{ maxWait: 2000 } | ||
); | ||
// When the component goes to be unmounted, we will fetch data if the input has changed. | ||
useEffect( | ||
() => () => { | ||
callPending(); | ||
}, | ||
[] | ||
); | ||
return <input defaultValue={defaultValue} onChange={(e) => debouncedFunction(e.target.value)} />; | ||
} | ||
``` |
@@ -6,2 +6,2 @@ export default function useDebouncedCallback<T extends (...args: any[]) => any>( | ||
options?: { maxWait?: number } | ||
): [T, () => void]; | ||
): [T, () => void, () => void]; |
@@ -8,2 +8,3 @@ import { useCallback, useEffect, useRef } from 'react'; | ||
const functionTimeoutHandler = useRef(null); | ||
const isComponentUnmounted = useRef(false); | ||
@@ -17,2 +18,3 @@ const debouncedFunction = useCallback(callback, deps); | ||
maxWaitArgs.current = []; | ||
functionTimeoutHandler.current = null; | ||
}, [functionTimeoutHandler.current, maxWaitHandler.current]); | ||
@@ -22,3 +24,4 @@ | ||
() => () => { | ||
cancelDebouncedCallback(); | ||
// we use flag, as we allow to call callPending outside the hook | ||
isComponentUnmounted.current = true; | ||
}, | ||
@@ -32,3 +35,5 @@ [] | ||
functionTimeoutHandler.current = setTimeout(() => { | ||
debouncedFunction(...args); | ||
if (!isComponentUnmounted.current) { | ||
debouncedFunction(...args); | ||
} | ||
@@ -40,3 +45,5 @@ cancelDebouncedCallback(); | ||
maxWaitHandler.current = setTimeout(() => { | ||
debouncedFunction(...maxWaitArgs.current); | ||
if (!isComponentUnmounted.current) { | ||
debouncedFunction(...maxWaitArgs.current); | ||
} | ||
cancelDebouncedCallback(); | ||
@@ -47,3 +54,14 @@ }, maxWait); | ||
return [debouncedCallback, cancelDebouncedCallback]; | ||
const callPending = () => { | ||
// Call pending callback only if we have anything in our queue | ||
if (!functionTimeoutHandler.current) { | ||
return; | ||
} | ||
debouncedFunction(...maxWaitArgs.current); | ||
cancelDebouncedCallback(); | ||
}; | ||
// For the moment, we use 3 args array so that we save backward compatibility | ||
return [debouncedCallback, cancelDebouncedCallback, callPending]; | ||
} |
import Enzyme from 'enzyme'; | ||
import React from 'react'; | ||
import React, { useEffect } from 'react'; | ||
import useDebouncedCallback from '../src/callback'; | ||
@@ -195,2 +195,100 @@ import { act } from 'react-dom/test-utils'; | ||
}); | ||
it('will call pending callback if callPending function is called', () => { | ||
const callback = jest.fn(); | ||
function Component({ text }) { | ||
const [debouncedCallback, , callPending] = useDebouncedCallback(callback, 500, []); | ||
debouncedCallback(); | ||
if (text === 'test') { | ||
callPending(); | ||
} | ||
return <span>{text}</span>; | ||
} | ||
const tree = Enzyme.mount(<Component text="one" />); | ||
expect(callback.mock.calls.length).toBe(0); | ||
expect(tree.text()).toBe('one'); | ||
act(() => { | ||
tree.setProps({ text: 'test' }); | ||
}); | ||
expect(callback.mock.calls.length).toBe(1); | ||
}); | ||
it('won\t call pending callback if callPending function is called and there are no items in queue', () => { | ||
const callback = jest.fn(); | ||
function Component({ text }) { | ||
const [debouncedCallback, , callPending] = useDebouncedCallback(callback, 500, []); | ||
if (text === 'test') { | ||
callPending(); | ||
} | ||
return <span>{text}</span>; | ||
} | ||
const tree = Enzyme.mount(<Component text="one" />); | ||
expect(callback.mock.calls.length).toBe(0); | ||
expect(tree.text()).toBe('one'); | ||
act(() => { | ||
tree.setProps({ text: 'test' }); | ||
}); | ||
expect(callback.mock.calls.length).toBe(0); | ||
expect(tree.text()).toBe('test'); | ||
}); | ||
it('won\t call pending callback if callPending function is called and cancel method is also executed', () => { | ||
const callback = jest.fn(); | ||
function Component({ text }) { | ||
const [debouncedCallback, cancel, callPending] = useDebouncedCallback(callback, 500, []); | ||
debouncedCallback(); | ||
if (text === 'test') { | ||
cancel(); | ||
callPending(); | ||
} | ||
return <span>{text}</span>; | ||
} | ||
const tree = Enzyme.mount(<Component text="one" />); | ||
expect(callback.mock.calls.length).toBe(0); | ||
expect(tree.text()).toBe('one'); | ||
act(() => { | ||
tree.setProps({ text: 'test' }); | ||
}); | ||
expect(callback.mock.calls.length).toBe(0); | ||
expect(tree.text()).toBe('test'); | ||
}); | ||
it('will call pending callback if callPending function is called on component unmount', () => { | ||
const callback = jest.fn(); | ||
function Component({ text }) { | ||
const [debouncedCallback, , callPending] = useDebouncedCallback(callback, 500, []); | ||
debouncedCallback(); | ||
useEffect( | ||
() => () => { | ||
callPending(); | ||
}, | ||
[] | ||
); | ||
return <span>{text}</span>; | ||
} | ||
const tree = Enzyme.mount(<Component text="one" />); | ||
expect(callback.mock.calls.length).toBe(0); | ||
expect(tree.text()).toBe('one'); | ||
act(() => { | ||
tree.unmount(); | ||
}); | ||
expect(callback.mock.calls.length).toBe(1); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
214995
453
0
190
17