Skip to content

Commit

Permalink
feat: add useAsyncCallback hook
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich authored Apr 8, 2019
2 parents 2dd6dc1 + f139b9f commit c6ecb36
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<br/>
- [**Side-effects**](./docs/Side-effects.md)
- [`useAsync`](./docs/useAsync.md) &mdash; resolves an `async` function.
- [`useAsyncCallback`](./docs/useAsyncCallback.md) &mdash; state management for async callback
- [`useAsyncRetry`](./docs/useAsyncRetry.md) &mdash; `useAsync` with `retry()` method.
- [`useCopyToClipboard`](./docs/useCopyToClipboard.md) &mdash; copies text to clipboard.
- [`useDebounce`](./docs/useDebounce.md) &mdash; debounces a function. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-usedebounce--demo)
Expand Down
36 changes: 36 additions & 0 deletions docs/useAsyncCallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# `useAsyncCallback`

React hook that returns state and a callback for an `async` function or a
function that returns a promise. The state is of the same shape as `useAsync`.

## Usage

```jsx
import {useAsyncCallback} from 'react-use';

const Demo = (url) => {
const [state, fetch] = useAsyncCallback(async () => {
const response = await fetch(url);
const result = await response.text();
return result
}), [url];

return (
<div>
{state.loading
? <div>Loading...</div>
: state.error
? <div>Error: {state.error.message}</div>
: state.value && <div>Value: {state.value}</div>
}
<button onClick={() => fetch()}>Start loading</button>
</div>
);
};
```

## Reference

```ts
useAsyncCallback(fn, deps?: any[]);
```
35 changes: 35 additions & 0 deletions src/__stories__/useAsyncCallback.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import {storiesOf} from '@storybook/react';
import {useAsyncCallback} from '..';
import ShowDocs from './util/ShowDocs';

const fn = () =>
new Promise<string>((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
reject(new Error('Random error!'));
} else {
resolve('RESOLVED');
}
}, 1000);
});

const Demo = () => {
const [{loading, error, value}, callback] = useAsyncCallback<string>(fn);

return (
<div>
{loading
? <div>Loading...</div>
: error
? <div>Error: {error.message}</div>
: value && <div>Value: {value}</div>
}
<button onClick={() => callback()}>Start</button>
</div>
);
};

storiesOf('Side effects|useAsyncCallback', module)
.add('Docs', () => <ShowDocs md={require('../../docs/useAsyncCallback.md')} />)
.add('Demo', () => <Demo/>)
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import createMemo from './createMemo';
import useAsync from './useAsync';
import useAsyncCallback from './useAsyncCallback';
import useAsyncRetry from './useAsyncRetry';
import useAudio from './useAudio';
import useBattery from './useBattery';
Expand Down Expand Up @@ -71,6 +72,7 @@ import useUpdateEffect from './useUpdateEffect'
export {
createMemo,
useAsync,
useAsyncCallback,
useAsyncRetry,
useAudio,
useBattery,
Expand Down
48 changes: 48 additions & 0 deletions src/useAsyncCallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState, useEffect, useCallback, DependencyList } from 'react';
import useRefMounted from "./useRefMounted"

export type AsyncState<T> =
| {
loading: boolean;
error?: undefined;
value?: undefined;
}
| {
loading: false;
error: Error;
value?: undefined;
}
| {
loading: false;
error?: undefined;
value: T;
};

const useAsyncCallback = <T>(fn: () => Promise<T>, deps: DependencyList = []): [AsyncState<T>, () => void] => {
const [state, set] = useState<AsyncState<T>>({
loading: false
});

const mounted = useRefMounted();

const callback = useCallback(() => {
set({loading: true});

fn().then(
value => {
if (mounted.current) {
set({value, loading: false});
}
},
error => {
if (mounted.current) {
set({error, loading: false});
}
}
);
}, deps);

return [state, callback]
};

export default useAsyncCallback;

0 comments on commit c6ecb36

Please sign in to comment.