mirror of
https://github.com/streamich/react-use.git
synced 2026-01-25 14:17:16 +00:00
Merge pull request #824 from streamich/useUnmountPromise
feat: 🎸 add useUnmountPromise hook
This commit is contained in:
commit
6aa9984a3a
@ -111,7 +111,7 @@
|
||||
- [`useMountedState`](./docs/useMountedState.md) — track 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.
|
||||
- [`useMount`](./docs/useMount.md) and [`useUnmountPromise`](./docs/useUnmountPromise.md) — tracks if component is mounted.
|
||||
- [`useUnmount`](./docs/useUnmount.md) — calls `unmount` callbacks.
|
||||
- [`useUpdateEffect`](./docs/useUpdateEffect.md) — run an `effect` only on updates.
|
||||
- [`useIsomorphicLayoutEffect`](./docs/useIsomorphicLayoutEffect.md) — `useLayoutEffect` that does not show warning when server-side rendering.
|
||||
|
||||
30
docs/useUnmountPromise.md
Normal file
30
docs/useUnmountPromise.md
Normal file
@ -0,0 +1,30 @@
|
||||
# `useUnmountPromise`
|
||||
|
||||
A life-cycle hook that provides a higher order promise that does not resolve if component un-mounts.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import useUnmountPromise from 'react-use/lib/useUnmountPromise';
|
||||
|
||||
const Demo = () => {
|
||||
const mounted = useUnmountPromise();
|
||||
useEffect(async () => {
|
||||
await mounted(someFunction()); // Will not resolve if component un-mounts.
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
```ts
|
||||
const mounted = useUnmountPromise();
|
||||
|
||||
mounted(promise);
|
||||
mounted(promise, onError);
|
||||
```
|
||||
|
||||
- `onError` — if promise rejects after the component is unmounted, `onError`
|
||||
callback is called with the error.
|
||||
@ -85,6 +85,7 @@ export { default as useTitle } from './useTitle';
|
||||
export { default as useToggle } from './useToggle';
|
||||
export { default as useTween } from './useTween';
|
||||
export { default as useUnmount } from './useUnmount';
|
||||
export { default as useUnmountPromise } from './useUnmountPromise';
|
||||
export { default as useUpdate } from './useUpdate';
|
||||
export { default as useUpdateEffect } from './useUpdateEffect';
|
||||
export { default as useUpsert } from './useUpsert';
|
||||
|
||||
33
src/useUnmountPromise.ts
Normal file
33
src/useUnmountPromise.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useMemo, useRef, useEffect } from 'react';
|
||||
|
||||
export type Race = <P extends Promise<any>, E = any>(promise: P, onError?: (error: E) => void) => P;
|
||||
|
||||
const useUnmountPromise = (): Race => {
|
||||
const refUnmounted = useRef(false);
|
||||
useEffect(() => () => {
|
||||
refUnmounted.current = true;
|
||||
});
|
||||
|
||||
const wrapper = useMemo(() => {
|
||||
const race = <P extends Promise<any>, E>(promise: P, onError?: (error: E) => void) => {
|
||||
const newPromise: P = new Promise((resolve, reject) => {
|
||||
promise.then(
|
||||
result => {
|
||||
if (!refUnmounted.current) resolve(result);
|
||||
},
|
||||
error => {
|
||||
if (!refUnmounted.current) reject(error);
|
||||
else if (onError) onError(error);
|
||||
else console.error('useUnmountPromise', error);
|
||||
}
|
||||
);
|
||||
}) as P;
|
||||
return newPromise;
|
||||
};
|
||||
return race;
|
||||
}, []);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
export default useUnmountPromise;
|
||||
79
tests/useUnmountPromise.test.ts
Normal file
79
tests/useUnmountPromise.test.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import useUnmountPromise from '../src/useUnmountPromise';
|
||||
|
||||
describe('useUnmountPromise', () => {
|
||||
it('should be defined', () => {
|
||||
expect(useUnmountPromise).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return a function', () => {
|
||||
const hook = renderHook(() => useUnmountPromise());
|
||||
|
||||
expect(typeof hook.result.current).toBe('function');
|
||||
});
|
||||
|
||||
it('when component is mounted function should resolve with wrapped promises', async () => {
|
||||
const hook = renderHook(() => useUnmountPromise());
|
||||
|
||||
const mounted = hook.result.current;
|
||||
const res = await mounted(new Promise(r => setTimeout(() => r(25), 10)));
|
||||
|
||||
expect(res).toBe(25);
|
||||
});
|
||||
|
||||
it('when component is unmounted promise never resolves', async () => {
|
||||
const hook = renderHook(() => useUnmountPromise());
|
||||
|
||||
const mounted = hook.result.current;
|
||||
const promise = mounted(new Promise(r => setTimeout(() => r(25), 10)));
|
||||
|
||||
hook.unmount();
|
||||
|
||||
const res = await Promise.race([promise, new Promise(r => setTimeout(() => r('UNMOUNTED'), 20))]);
|
||||
expect(res).toBe('UNMOUNTED');
|
||||
});
|
||||
|
||||
it('when component is mounted function should resolve with wrapped promises - 2', async () => {
|
||||
const hook = renderHook(() => useUnmountPromise());
|
||||
|
||||
const mounted = hook.result.current;
|
||||
const promise = mounted(new Promise(r => setTimeout(() => r(25), 10)));
|
||||
|
||||
// hook.unmount();
|
||||
|
||||
const res = await Promise.race([promise, new Promise(r => setTimeout(() => r('UNMOUNTED'), 20))]);
|
||||
expect(res).toBe(25);
|
||||
});
|
||||
|
||||
describe('when promise throws', () => {
|
||||
describe('when component is mounted', () => {
|
||||
it('onError callback is not called', async () => {
|
||||
const hook = renderHook(() => useUnmountPromise());
|
||||
|
||||
const mounted = hook.result.current;
|
||||
const onError = jest.fn();
|
||||
try {
|
||||
await mounted(new Promise((r, reject) => setTimeout(() => reject(r), 10)), onError);
|
||||
} catch {}
|
||||
|
||||
expect(onError).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when component is un-mounted', () => {
|
||||
it('onError callback is called', async () => {
|
||||
const hook = renderHook(() => useUnmountPromise());
|
||||
|
||||
const mounted = hook.result.current;
|
||||
const onError = jest.fn();
|
||||
const promise = mounted(new Promise((r, reject) => setTimeout(() => reject(r), 10)), onError);
|
||||
|
||||
hook.unmount();
|
||||
await Promise.race([promise, new Promise(r => setTimeout(r, 20))]);
|
||||
|
||||
expect(onError).toHaveBeenCalledTimes(1);
|
||||
expect(typeof onError.mock.calls[0][0]).toBe('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user