Skip to content

Commit c6ecb36

Browse files
authored
feat: add useAsyncCallback hook
2 parents 2dd6dc1 + f139b9f commit c6ecb36

File tree

5 files changed

+122
-0
lines changed

5 files changed

+122
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
<br/>
7474
- [**Side-effects**](./docs/Side-effects.md)
7575
- [`useAsync`](./docs/useAsync.md) &mdash; resolves an `async` function.
76+
- [`useAsyncCallback`](./docs/useAsyncCallback.md) &mdash; state management for async callback
7677
- [`useAsyncRetry`](./docs/useAsyncRetry.md) &mdash; `useAsync` with `retry()` method.
7778
- [`useCopyToClipboard`](./docs/useCopyToClipboard.md) &mdash; copies text to clipboard.
7879
- [`useDebounce`](./docs/useDebounce.md) &mdash; debounces a function. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-usedebounce--demo)

docs/useAsyncCallback.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# `useAsyncCallback`
2+
3+
React hook that returns state and a callback for an `async` function or a
4+
function that returns a promise. The state is of the same shape as `useAsync`.
5+
6+
## Usage
7+
8+
```jsx
9+
import {useAsyncCallback} from 'react-use';
10+
11+
const Demo = (url) => {
12+
const [state, fetch] = useAsyncCallback(async () => {
13+
const response = await fetch(url);
14+
const result = await response.text();
15+
return result
16+
}), [url];
17+
18+
return (
19+
<div>
20+
{state.loading
21+
? <div>Loading...</div>
22+
: state.error
23+
? <div>Error: {state.error.message}</div>
24+
: state.value && <div>Value: {state.value}</div>
25+
}
26+
<button onClick={() => fetch()}>Start loading</button>
27+
</div>
28+
);
29+
};
30+
```
31+
32+
## Reference
33+
34+
```ts
35+
useAsyncCallback(fn, deps?: any[]);
36+
```
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as React from 'react';
2+
import {storiesOf} from '@storybook/react';
3+
import {useAsyncCallback} from '..';
4+
import ShowDocs from './util/ShowDocs';
5+
6+
const fn = () =>
7+
new Promise<string>((resolve, reject) => {
8+
setTimeout(() => {
9+
if (Math.random() > 0.5) {
10+
reject(new Error('Random error!'));
11+
} else {
12+
resolve('RESOLVED');
13+
}
14+
}, 1000);
15+
});
16+
17+
const Demo = () => {
18+
const [{loading, error, value}, callback] = useAsyncCallback<string>(fn);
19+
20+
return (
21+
<div>
22+
{loading
23+
? <div>Loading...</div>
24+
: error
25+
? <div>Error: {error.message}</div>
26+
: value && <div>Value: {value}</div>
27+
}
28+
<button onClick={() => callback()}>Start</button>
29+
</div>
30+
);
31+
};
32+
33+
storiesOf('Side effects|useAsyncCallback', module)
34+
.add('Docs', () => <ShowDocs md={require('../../docs/useAsyncCallback.md')} />)
35+
.add('Demo', () => <Demo/>)

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import createMemo from './createMemo';
22
import useAsync from './useAsync';
3+
import useAsyncCallback from './useAsyncCallback';
34
import useAsyncRetry from './useAsyncRetry';
45
import useAudio from './useAudio';
56
import useBattery from './useBattery';
@@ -71,6 +72,7 @@ import useUpdateEffect from './useUpdateEffect'
7172
export {
7273
createMemo,
7374
useAsync,
75+
useAsyncCallback,
7476
useAsyncRetry,
7577
useAudio,
7678
useBattery,

src/useAsyncCallback.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useState, useEffect, useCallback, DependencyList } from 'react';
2+
import useRefMounted from "./useRefMounted"
3+
4+
export type AsyncState<T> =
5+
| {
6+
loading: boolean;
7+
error?: undefined;
8+
value?: undefined;
9+
}
10+
| {
11+
loading: false;
12+
error: Error;
13+
value?: undefined;
14+
}
15+
| {
16+
loading: false;
17+
error?: undefined;
18+
value: T;
19+
};
20+
21+
const useAsyncCallback = <T>(fn: () => Promise<T>, deps: DependencyList = []): [AsyncState<T>, () => void] => {
22+
const [state, set] = useState<AsyncState<T>>({
23+
loading: false
24+
});
25+
26+
const mounted = useRefMounted();
27+
28+
const callback = useCallback(() => {
29+
set({loading: true});
30+
31+
fn().then(
32+
value => {
33+
if (mounted.current) {
34+
set({value, loading: false});
35+
}
36+
},
37+
error => {
38+
if (mounted.current) {
39+
set({error, loading: false});
40+
}
41+
}
42+
);
43+
}, deps);
44+
45+
return [state, callback]
46+
};
47+
48+
export default useAsyncCallback;

0 commit comments

Comments
 (0)