From aad368ba6768cb71fd325f7579181f46365cbc96 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 15 Mar 2019 15:14:00 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20usePromise()=20hoo?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/usePromise.md | 24 ++++++++++++++++ src/__stories__/usePromise.story.tsx | 42 ++++++++++++++++++++++++++++ src/index.ts | 2 ++ src/usePromise.ts | 22 +++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 docs/usePromise.md create mode 100644 src/__stories__/usePromise.story.tsx create mode 100644 src/usePromise.ts diff --git a/README.md b/README.md index cf632c61..18c3cb95 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ - [**Lifecycles**](./docs/Lifecycles.md) - [`useLifecycles`](./docs/useLifecycles.md) — calls `mount` and `unmount` callbacks. - [`useRefMounted`](./docs/useRefMounted.md) — tracks if component is mounted. + - [`usePromise`](./docs/usePromise.md) — resolves promise only while component is mounted. - [`useLogger`](./docs/useLogger.md) — logs in console as component goes through life-cycles. - [`useMount`](./docs/useMount.md) — calls `mount` callbacks. - [`useUnmount`](./docs/useUnmount.md) — calls `unmount` callbacks. diff --git a/docs/usePromise.md b/docs/usePromise.md new file mode 100644 index 00000000..b3c1fbfa --- /dev/null +++ b/docs/usePromise.md @@ -0,0 +1,24 @@ +# `usePromise` + +React Lifecycle hook that returns a helper function for wrapping promises. +Promises wrapped with this function will resolve only when component is mounted. + + +## Usage + +```jsx +import {usePromise} from 'react-use'; + +const Demo = ({promise}) => { + const mounted = usePromise(); + const [value, setValue] = useState(); + + useEffect(() => { + (async () => { + const value = await mounted(promise); + // This line will not execute if component gets unmounted. + setValue(value); + })(); + }); +}; +``` diff --git a/src/__stories__/usePromise.story.tsx b/src/__stories__/usePromise.story.tsx new file mode 100644 index 00000000..c81e3396 --- /dev/null +++ b/src/__stories__/usePromise.story.tsx @@ -0,0 +1,42 @@ +import {storiesOf} from '@storybook/react'; +import * as React from 'react'; +import {usePromise, useBoolean, useNumber} from '..'; +import ShowDocs from '../util/ShowDocs'; + +const {useState, useEffect} = React; + +const DemoInner = ({promise}) => { + const safePromise = usePromise(); + const [value, setValue] = useState(-1); + useEffect(() => { + safePromise(promise).then(setValue); + }, [promise]); + + return ( +
+ {value === -1 ? 'Resolving value...' : 'Value: ' + value} +
+ ); +}; + +const Demo = () => { + const [mounted, toggleMounted] = useBoolean(true); + const [num, {inc}] = useNumber(); + const promise = new Promise(r => setTimeout(() => r(num), 1_000)); + + return ( +
+

This demo provides a number in a promise that resolves in 1sec to a child component.

+ + +
+ {mounted && } +
+ ); +}; + +storiesOf('Lifecycles|usePromise', module) + .add('Docs', () => ) + .add('Demo', () => + + ) diff --git a/src/index.ts b/src/index.ts index 1509586e..7e5dd099 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,7 @@ import useNumber from './useNumber'; import useObservable from './useObservable'; import useOrientation from './useOrientation'; import useOutsideClick from './useOutsideClick'; +import usePromise from './usePromise'; import useRaf from './useRaf'; import useRefMounted from './useRefMounted'; import useSessionStorage from './useSessionStorage'; @@ -80,6 +81,7 @@ export { useObservable, useOrientation, useOutsideClick, + usePromise, useRaf, useRefMounted, useSessionStorage, diff --git a/src/usePromise.ts b/src/usePromise.ts new file mode 100644 index 00000000..1a827b25 --- /dev/null +++ b/src/usePromise.ts @@ -0,0 +1,22 @@ +import {useCallback} from 'react'; +import useRefMounted from './useRefMounted'; + +export type UsePromise = () => (promise: Promise) => Promise; + +const usePromise: UsePromise = () => { + const refMounted = useRefMounted(); + return useCallback((promise: Promise): Promise => + new Promise((resolve, reject) => { + promise.then(value => { + if (refMounted.current) { + resolve(value); + } + }, error => { + if (refMounted.current) { + reject(error); + } + }) + }), []); +}; + +export default usePromise;