diff --git a/README.md b/README.md index 73793bb9..bea8dc23 100644 --- a/README.md +++ b/README.md @@ -101,12 +101,12 @@ - [`useTitle`](./docs/useTitle.md) — sets title of the page. - [`usePermission`](./docs/usePermission.md) — query permission status for browser APIs.
-
+
- [**Lifecycles**](./docs/Lifecycles.md) - [`useEffectOnce`](./docs/useEffectOnce.md) — a modified [`useEffect`](https://reactjs.org/docs/hooks-reference.html#useeffect) hook that only runs once. - [`useEvent`](./docs/useEvent.md) — subscribe to events. - [`useLifecycles`](./docs/useLifecycles.md) — calls `mount` and `unmount` callbacks. - - [`useRefMounted`](./docs/useRefMounted.md) — tracks if component is mounted. + - [`useMountedState`](./docs/useMountedState.md) and [`useRefMounted`](./docs/useRefMounted.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. diff --git a/docs/useMountedState.md b/docs/useMountedState.md new file mode 100644 index 00000000..ae6ecace --- /dev/null +++ b/docs/useMountedState.md @@ -0,0 +1,25 @@ +# `useMountedState` + +Lifecycle hook providing ability to check component's mount state. +Gives a function that will return `true` if component mounted and `false` otherwise. + +## Usage + +```jsx +import * as React from 'react'; +import {useMountedState} from 'react-use'; + +const Demo = () => { + const isMounted = useMountedState(); + + React.useEffect(() => { + setTimeout(() => { + if (isMounted()) { + // ... + } else { + // ... + } + }, 1000); + }); +}; +``` diff --git a/src/__stories__/useMountedState.story.tsx b/src/__stories__/useMountedState.story.tsx new file mode 100644 index 00000000..a4bcac43 --- /dev/null +++ b/src/__stories__/useMountedState.story.tsx @@ -0,0 +1,17 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import { useMountedState } from '..'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const isMounted = useMountedState(); + const [, updateState] = React.useState(); + + requestAnimationFrame(updateState); + + return
This component is {isMounted() ? 'MOUNTED' : 'NOT MOUNTED'}
; +}; + +storiesOf('Lifecycle|useMountedState', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/__tests__/useMountedState.test.tsx b/src/__tests__/useMountedState.test.tsx new file mode 100644 index 00000000..56981a14 --- /dev/null +++ b/src/__tests__/useMountedState.test.tsx @@ -0,0 +1,28 @@ +import { renderHook } from 'react-hooks-testing-library'; +import useMountedState from '../useMountedState'; + +describe('useMountedState', () => { + it('should be defined', () => { + expect(useMountedState).toBeDefined(); + }); + + it('should return a function', () => { + const hook = renderHook(() => useMountedState(), { initialProps: false }); + + expect(typeof hook.result.current).toEqual('function'); + }); + + it('should return true if component is mounted', () => { + const hook = renderHook(() => useMountedState(), { initialProps: false }); + + expect(hook.result.current()).toBeTruthy(); + }); + + it('should return false if component is unmounted', () => { + const hook = renderHook(() => useMountedState(), { initialProps: false }); + + hook.unmount(); + + expect(hook.result.current()).toBeFalsy(); + }); +}); diff --git a/src/index.ts b/src/index.ts index b9e84735..21bd4fe2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,7 @@ export { default as useMedia } from './useMedia'; export { default as useMediaDevices } from './useMediaDevices'; export { default as useMotion } from './useMotion'; export { default as useMount } from './useMount'; +export { default as useMountedState } from './useMountedState'; export { default as useMouse } from './useMouse'; export { default as useMouseHovered } from './useMouseHovered'; export { default as useNetwork } from './useNetwork'; diff --git a/src/useMountedState.ts b/src/useMountedState.ts new file mode 100644 index 00000000..6236d415 --- /dev/null +++ b/src/useMountedState.ts @@ -0,0 +1,15 @@ +import { useEffect, useRef } from 'react'; + +export default function useMountedState(): () => boolean { + const mountedRef = useRef(false); + + useEffect(() => { + mountedRef.current = true; + + return () => { + mountedRef.current = false; + }; + }); + + return () => mountedRef.current; +}