Skip to content

Commit

Permalink
Merge pull request #415 from little-buddy/master
Browse files Browse the repository at this point in the history
refactor(hook): useWillUnmount impl by useLayoutEffect

BREAKING CHANGE: renames useWillUnmount into useUnmount
  • Loading branch information
antonioru committed Aug 8, 2023
2 parents 519ebf1 + 5208905 commit 2157311
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -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)
Expand Down
95 changes: 95 additions & 0 deletions 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 (
<DisplayDemo title="useUnmount">
<Typography.Paragraph>Check the javascript console complete moving from this page</Typography.Paragraph>
</DisplayDemo>
);
};

<ComponentUnmount />;
```

### 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 (
<DisplayDemo title="useUnmount">
<Typography.Paragraph>
Check the javascript console complete moving from this page
</Typography.Paragraph>
</DisplayDemo>
);
};

<ComponentUnmount />;
```

#### ✅ 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 -->
### 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: <TCallback extends GenericFunction>(callback?: TCallback | undefined) => import("./shared/types").CallbackSetter<undefined>;
export default useUnmount;

```

<!-- Types:end -->
16 changes: 8 additions & 8 deletions docs/useWillUnmount.md
Expand Up @@ -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:

Expand All @@ -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 (
Expand Down Expand Up @@ -43,7 +43,7 @@ const ComponentWillUnmount = () => {
const onUnmount = useWillUnmount();

onUnmount(() => {
console.log('Component did unmount');
console.log('Component will unmount');
});

return (
Expand All @@ -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

Expand Down
28 changes: 28 additions & 0 deletions 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 = <TCallback extends GenericFunction>(
callback?: TCallback
) => {
const mountRef = useRef(false);
const [handler, setHandler] = createHandlerSetter<undefined>(callback);

useEffect(() => {
mountRef.current = true;

return () => {
if (isFunction(handler?.current) && mountRef.current) {
handler.current();
}
};
}, []);

return setHandler;
};

export default useUnmount;
4 changes: 2 additions & 2 deletions 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'
Expand All @@ -10,7 +10,7 @@ const useWillUnmount = <TCallback extends GenericFunction>(callback?: TCallback)
const mountRef = useRef(false)
const [handler, setHandler] = createHandlerSetter<undefined>(callback)

useEffect(() => {
useLayoutEffect(() => {
mountRef.current = true

return () => {
Expand Down
38 changes: 38 additions & 0 deletions 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(<TestComponent />)

rerender(null)

expect(spy.called).to.be.true
})
})

0 comments on commit 2157311

Please sign in to comment.