diff --git a/README.md b/README.md index f55efd9..ab93a63 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ import useSomeHook from 'beautiful-react-hooks/useSomeHook' * [useConditionalTimeout](docs/useConditionalTimeout.md) * [useCookie](docs/useCookie.md) * [useDarkMode](docs/useDarkMode.md) +* [useUnmount](docs/useUnmount.md) * [useUpdateEffect](docs/useUpdateEffect.md) * [useIsFirstRender](docs/useIsFirstRender.md) * [useMutationObserver](docs/useMutationObserver.md) diff --git a/docs/useUnmount.md b/docs/useUnmount.md new file mode 100644 index 0000000..c5a282c --- /dev/null +++ b/docs/useUnmount.md @@ -0,0 +1,95 @@ +# useUnmount + +A hook that takes in a function to execute right when the component unmounts. + +### Why? 💡 + +- takes care of performing a callback when the component unmounts + +### Basic Usage: + +```jsx harmony +import { Typography } from 'antd'; +import useUnmount from 'beautiful-react-hooks/useUnmount'; + +const ComponentUnmount = () => { + useUnmount(() => { + console.log('Component did unmount'); + }); + + return ( + + Check the javascript console complete moving from this page + + ); +}; + +; +``` + +### Callback setter syntax: + +If the first parameter is omitted, you can use the returned function (a callback setter) to set the useWillUnmount handler. However, you +must immediately invoke the callback setter. + +Important: The callback setter only changes the value of the callback reference and does not trigger a component rerender. Also, avoid +invoking it asynchronously + +```jsx harmony +import { Typography } from 'antd'; +import useUnmount from 'beautiful-react-hooks/useUnmount'; + +const ComponentUnmount = () => { + const onUnmount = useUnmount(); + + onUnmount(() => { + console.log('Component did unmount'); + }); + + return ( + + + Check the javascript console complete moving from this page + + + ); +}; + +; +``` + +#### ✅ Pro tip: + +When using a React function component you should not really think of it in terms of "lifecycle". + +The `useUnmount` hook is indeed intended as a shortcut to `useEffect(() => () => Unmount, [])`. + +To deep understanding `useEffect`, what it is and how it should be properly used, please read +"[A complete guide to useEffect](https://overreacted.io/a-complete-guide-to-useeffect/)" +by [Dan Abramov](https://twitter.com/dan_abramov) + +### Mastering the hook + +#### ✅ When to use + +- When you need to perform a function after the component has mounted + +#### 🛑 When not to use + +- Avoid using this hook asynchronously since it would violate the [rules of hooks](https://reactjs.org/docs/hooks-rules.html) +- If you're using the callback setter, make sure to invoke it immediately instead of asynchronously + + +### Types + +```typescript static +import { type GenericFunction } from './shared/types'; +/** + * Returns a callback setter for a callback to be performed when the component did unmount. + */ +declare const useUnmount: (callback?: TCallback | undefined) => import("./shared/types").CallbackSetter; +export default useUnmount; + +``` + + diff --git a/docs/useWillUnmount.md b/docs/useWillUnmount.md index 2084f14..5aed7c9 100644 --- a/docs/useWillUnmount.md +++ b/docs/useWillUnmount.md @@ -4,7 +4,7 @@ A hook that takes in a function to execute right before the component unmounts. ### Why? 💡 -- takes care of performing a callback when the component unmounts +- takes care of performing a callback before the component unmounts ### Basic Usage: @@ -14,7 +14,7 @@ import useWillUnmount from 'beautiful-react-hooks/useWillUnmount'; const ComponentWillUnmount = () => { useWillUnmount(() => { - console.log('Component did unmount'); + console.log('Component will unmount'); }); return ( @@ -43,7 +43,7 @@ const ComponentWillUnmount = () => { const onUnmount = useWillUnmount(); onUnmount(() => { - console.log('Component did unmount'); + console.log('Component will unmount'); }); return ( @@ -60,17 +60,17 @@ const ComponentWillUnmount = () => { When using a React function component you should not really think of it in terms of "lifecycle". -The `useWillUnmount` hook is indeed intended as a shortcut to `useEffect(() => () => willUnmount, [])`. +The `useWillUnmount` hook is indeed intended as a shortcut to `useLayoutEffect(() => () => willUnmount, [])`. -To deep understanding `useEffect`, what it is and how it should be properly used, please read -"[A complete guide to useEffect](https://overreacted.io/a-complete-guide-to-useeffect/)" -by [Dan Abramov](https://twitter.com/dan_abramov) +To deep understanding `useLayoutEffect`, what it is and how it should be properly used, please read +"[A complete guide to useLayoutEffect](https://react.dev/reference/react/useLayoutEffect)" +by React Team ### Mastering the hook #### ✅ When to use -- When you need to perform a function after the component has mounted +- When you need to perform a function before the component has mounted #### 🛑 When not to use diff --git a/src/useUnmount.ts b/src/useUnmount.ts new file mode 100644 index 0000000..7558de0 --- /dev/null +++ b/src/useUnmount.ts @@ -0,0 +1,28 @@ +import { useEffect, useRef } from "react"; +import isFunction from "./shared/isFunction"; +import { type GenericFunction } from "./shared/types"; +import createHandlerSetter from "./factory/createHandlerSetter"; + +/** + * Returns a callback setter for a callback to be performed when the component did unmount. + */ +const useUnmount = ( + callback?: TCallback +) => { + const mountRef = useRef(false); + const [handler, setHandler] = createHandlerSetter(callback); + + useEffect(() => { + mountRef.current = true; + + return () => { + if (isFunction(handler?.current) && mountRef.current) { + handler.current(); + } + }; + }, []); + + return setHandler; +}; + +export default useUnmount; diff --git a/src/useWillUnmount.ts b/src/useWillUnmount.ts index 95dd0ab..beb5ce4 100644 --- a/src/useWillUnmount.ts +++ b/src/useWillUnmount.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react' +import { useLayoutEffect, useRef } from 'react' import isFunction from './shared/isFunction' import { type GenericFunction } from './shared/types' import createHandlerSetter from './factory/createHandlerSetter' @@ -10,7 +10,7 @@ const useWillUnmount = (callback?: TCallback) const mountRef = useRef(false) const [handler, setHandler] = createHandlerSetter(callback) - useEffect(() => { + useLayoutEffect(() => { mountRef.current = true return () => { diff --git a/test/useUnmount.spec.js b/test/useUnmount.spec.js new file mode 100644 index 0000000..70911fc --- /dev/null +++ b/test/useUnmount.spec.js @@ -0,0 +1,38 @@ +import React from 'react' +import { cleanup as cleanupReact, render } from '@testing-library/react' +import { cleanup as cleanupHooks, renderHook } from '@testing-library/react-hooks' +import useUnmount from '../dist/useUnmount' +import assertHook from './utils/assertHook' + +describe('useUnmount', () => { + beforeEach(() => { + cleanupHooks() + cleanupReact() + }) + + assertHook(useUnmount) + + it('should return a single function', () => { + const { result } = renderHook(() => useUnmount()) + + expect(result.current).to.be.a('function') + }) + + it('the returned function should be a setter for a callback to be performed when component did unmount', () => { + const spy = sinon.spy() + + const TestComponent = () => { + const onUnmount = useUnmount() + + onUnmount(spy) + + return null + } + + const { rerender } = render() + + rerender(null) + + expect(spy.called).to.be.true + }) +})