Skip to content

Commit aad368b

Browse files
committed
feat: 🎸 add usePromise() hook
1 parent a2a6fe3 commit aad368b

File tree

5 files changed

+91
-0
lines changed

5 files changed

+91
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
- [**Lifecycles**](./docs/Lifecycles.md)
7777
- [`useLifecycles`](./docs/useLifecycles.md) — calls `mount` and `unmount` callbacks.
7878
- [`useRefMounted`](./docs/useRefMounted.md) — tracks if component is mounted.
79+
- [`usePromise`](./docs/usePromise.md) — resolves promise only while component is mounted.
7980
- [`useLogger`](./docs/useLogger.md) — logs in console as component goes through life-cycles.
8081
- [`useMount`](./docs/useMount.md) — calls `mount` callbacks.
8182
- [`useUnmount`](./docs/useUnmount.md) — calls `unmount` callbacks.

docs/usePromise.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# `usePromise`
2+
3+
React Lifecycle hook that returns a helper function for wrapping promises.
4+
Promises wrapped with this function will resolve only when component is mounted.
5+
6+
7+
## Usage
8+
9+
```jsx
10+
import {usePromise} from 'react-use';
11+
12+
const Demo = ({promise}) => {
13+
const mounted = usePromise();
14+
const [value, setValue] = useState();
15+
16+
useEffect(() => {
17+
(async () => {
18+
const value = await mounted(promise);
19+
// This line will not execute if <Demo> component gets unmounted.
20+
setValue(value);
21+
})();
22+
});
23+
};
24+
```

src/__stories__/usePromise.story.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {storiesOf} from '@storybook/react';
2+
import * as React from 'react';
3+
import {usePromise, useBoolean, useNumber} from '..';
4+
import ShowDocs from '../util/ShowDocs';
5+
6+
const {useState, useEffect} = React;
7+
8+
const DemoInner = ({promise}) => {
9+
const safePromise = usePromise();
10+
const [value, setValue] = useState<number>(-1);
11+
useEffect(() => {
12+
safePromise(promise).then(setValue);
13+
}, [promise]);
14+
15+
return (
16+
<div>
17+
{value === -1 ? 'Resolving value...' : 'Value: ' + value}
18+
</div>
19+
);
20+
};
21+
22+
const Demo = () => {
23+
const [mounted, toggleMounted] = useBoolean(true);
24+
const [num, {inc}] = useNumber();
25+
const promise = new Promise(r => setTimeout(() => r(num), 1_000));
26+
27+
return (
28+
<div>
29+
<p>This demo provides a number in a promise that resolves in 1sec to a child component.</p>
30+
<button onClick={() => toggleMounted()}>{mounted ? 'Unmount' : 'Mount'}</button>
31+
<button onClick={() => inc()}>Increment ({num})</button>
32+
<br />
33+
{mounted && <DemoInner promise={promise} />}
34+
</div>
35+
);
36+
};
37+
38+
storiesOf('Lifecycles|usePromise', module)
39+
.add('Docs', () => <ShowDocs md={require('../../docs/usePromise.md')} />)
40+
.add('Demo', () =>
41+
<Demo/>
42+
)

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import useNumber from './useNumber';
3030
import useObservable from './useObservable';
3131
import useOrientation from './useOrientation';
3232
import useOutsideClick from './useOutsideClick';
33+
import usePromise from './usePromise';
3334
import useRaf from './useRaf';
3435
import useRefMounted from './useRefMounted';
3536
import useSessionStorage from './useSessionStorage';
@@ -80,6 +81,7 @@ export {
8081
useObservable,
8182
useOrientation,
8283
useOutsideClick,
84+
usePromise,
8385
useRaf,
8486
useRefMounted,
8587
useSessionStorage,

src/usePromise.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {useCallback} from 'react';
2+
import useRefMounted from './useRefMounted';
3+
4+
export type UsePromise = () => <T>(promise: Promise<T>) => Promise<T>;
5+
6+
const usePromise: UsePromise = () => {
7+
const refMounted = useRefMounted();
8+
return useCallback(<T>(promise: Promise<T>): Promise<T> =>
9+
new Promise<T>((resolve, reject) => {
10+
promise.then(value => {
11+
if (refMounted.current) {
12+
resolve(value);
13+
}
14+
}, error => {
15+
if (refMounted.current) {
16+
reject(error);
17+
}
18+
})
19+
}), []);
20+
};
21+
22+
export default usePromise;

0 commit comments

Comments
 (0)