diff --git a/.gitignore b/.gitignore
index 523d37b356..0264808aec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,10 @@ pids
*.seed
*.pid.lock
+# IDE files
+.idea
+.vscode
+
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
@@ -71,4 +75,4 @@ src/parser.ts
.puppet-master/
storybook-static/
-package-lock.json
\ No newline at end of file
+package-lock.json
diff --git a/docs/useAsyncRetry.md b/docs/useAsyncRetry.md
new file mode 100644
index 0000000000..ff2a9ac0b3
--- /dev/null
+++ b/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 (
+
+ {state.loading?
+
Loading...
+ : state.error?
+
Error...
+ :
Value: {state.value}
+ }
+ {!state.loading?
+
state.retry()}>Retry
+ : null
+ }
+
+ );
+};
+```
+
+
+## Reference
+
+```ts
+useAsyncRetry(fn, args?: any[]);
+```
diff --git a/src/__stories__/useAsyncRetry.story.tsx b/src/__stories__/useAsyncRetry.story.tsx
new file mode 100644
index 0000000000..38b2985724
--- /dev/null
+++ b/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((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(fnRetry);
+
+ return (
+
+ {loading?
+
Loading...
+ : error?
+
Error: {error.message}
+ :
Value: {value}
+ }
+
retry()}>Retry
+
+ );
+};
+
+storiesOf('Side effects|useAsyncRetry', module)
+ .add('Docs', () => )
+ .add('Demo', () => );
diff --git a/src/index.ts b/src/index.ts
index 343a453352..66bdf580d9 100644
--- a/src/index.ts
+++ b/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';
@@ -52,6 +53,7 @@ import useWait from './useWait';
export {
createMemo,
useAsync,
+ useAsyncRetry,
useAudio,
useBattery,
useBoolean,
diff --git a/src/useAsync.ts b/src/useAsync.ts
index f9f2e1cc43..8749baec8a 100644
--- a/src/useAsync.ts
+++ b/src/useAsync.ts
@@ -1,51 +1,42 @@
-import {useState, useEffect, useCallback} from 'react';
+import { useState, useEffect, useCallback } from 'react';
-export type AsyncState =
-| {
- loading: true;
- error?: undefined;
- value?: undefined;
-}
-| {
- loading: false;
- error: Error;
- value?: undefined;
-}
-| {
- loading: false;
- error?: undefined;
- value: T;
+export interface AsyncState {
+ loading: boolean;
+ error?: Error;
+ value?: T;
};
const useAsync = (fn: () => Promise, args?) => {
const [state, set] = useState>({
- 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;
diff --git a/src/useAsyncRetry.ts b/src/useAsyncRetry.ts
new file mode 100644
index 0000000000..0382df244f
--- /dev/null
+++ b/src/useAsyncRetry.ts
@@ -0,0 +1,22 @@
+import { useCallback, useState } from 'react';
+import useAsync from './useAsync';
+
+const useAsyncRetry = (fn: () => Promise, args: any[] = []) => {
+ const [attempt, setAttempt] = useState(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;