Skip to content

Commit

Permalink
feat: add useAsyncRetry hook
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Mar 21, 2019
2 parents 4158ced + 8e3de1c commit 576cf42
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 25 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Expand Up @@ -11,6 +11,10 @@ pids
*.seed
*.pid.lock

# IDE files
.idea
.vscode

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

Expand Down Expand Up @@ -71,4 +75,4 @@ src/parser.ts
.puppet-master/

storybook-static/
package-lock.json
package-lock.json
47 changes: 47 additions & 0 deletions docs/useAsyncRetry.md
@@ -0,0 +1,47 @@
# `useAsyncRetry`

Uses `useAsync` with an additional `retry` method to easily retry/refresh the async function;


## Usage

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

// Returns a Promise that resolves after one second.
const fn = () => new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
reject(new Error('Random error!'));
} else {
resolve('RESOLVED');
}
}, 1000);
});

const Demo = () => {
const state = useAsync(fn);

return (
<div>
{state.loading?
<div>Loading...</div>
: state.error?
<div>Error...</div>
: <div>Value: {state.value}</div>
}
{!state.loading?
<a href='javascript:void 0' onClick={() => state.retry()}>Retry</a>
: null
}
</div>
);
};
```


## Reference

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

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

const DemoRetry = () => {
const { loading, value, error, retry } = useAsyncRetry<string>(fnRetry);

return (
<div>
{loading?
<div>Loading...</div>
: error?
<div>Error: {error.message}</div>
: <div>Value: {value}</div>
}
<a href='javascript:void 0' onClick={() => retry()}>Retry</a>
</div>
);
};

storiesOf('Side effects|useAsyncRetry', module)
.add('Docs', () => <ShowDocs md={require('../../docs/useAsyncRetry.md')} />)
.add('Demo', () => <DemoRetry />);
2 changes: 2 additions & 0 deletions src/index.ts
@@ -1,5 +1,6 @@
import createMemo from './createMemo';
import useAsync from './useAsync';
import useAsyncRetry from './useAsyncRetry';
import useAudio from './useAudio';
import useBattery from './useBattery';
import useBoolean from './useBoolean';
Expand Down Expand Up @@ -52,6 +53,7 @@ import useWait from './useWait';
export {
createMemo,
useAsync,
useAsyncRetry,
useAudio,
useBattery,
useBoolean,
Expand Down
39 changes: 15 additions & 24 deletions src/useAsync.ts
@@ -1,51 +1,42 @@
import {useState, useEffect, useCallback} from 'react';
import { useState, useEffect, useCallback } from 'react';

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

const useAsync = <T>(fn: () => Promise<T>, args?) => {
const [state, set] = useState<AsyncState<T>>({
loading: true,
loading: true
});
const memoized = useCallback(fn, args);

useEffect(() => {
let mounted = true;
set({
loading: true,
loading: true
});
const promise = memoized();

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

return () => {
mounted = false;
Expand Down
22 changes: 22 additions & 0 deletions src/useAsyncRetry.ts
@@ -0,0 +1,22 @@
import { useCallback, useState } from 'react';
import useAsync from './useAsync';

const useAsyncRetry = <T>(fn: () => Promise<T>, args: any[] = []) => {
const [attempt, setAttempt] = useState<number>(0);
const memoized = useCallback(() => fn(), [...args, attempt]);
const state = useAsync(memoized);

const retry = useCallback(() => {
if (state.loading) {
if (process.env.NODE_ENV === 'development') {
console.log('You are calling useAsyncRetry hook retry() method while loading in progress, this is a no-op.');
}
return;
}
setAttempt(attempt + 1);
}, [memoized, state, attempt]);

return { ...state, retry };
};

export default useAsyncRetry;

0 comments on commit 576cf42

Please sign in to comment.