Skip to content

Commit 96f3cc0

Browse files
committed
chore(utils): Added a simple useOnUnmount hook
1 parent 3ad1a3f commit 96f3cc0

File tree

3 files changed

+106
-25
lines changed

3 files changed

+106
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React, { useState } from "react";
2+
import { render } from "@testing-library/react";
3+
4+
import { useOnUnmount } from "../useOnUnmount";
5+
import userEvent from "@testing-library/user-event";
6+
7+
describe("useOnUnmount", () => {
8+
it("should work correctly", () => {
9+
const callback = jest.fn();
10+
function Test() {
11+
useOnUnmount(callback);
12+
13+
return null;
14+
}
15+
16+
const { unmount, rerender } = render(<Test />);
17+
expect(callback).not.toBeCalled();
18+
19+
rerender(<Test />);
20+
expect(callback).not.toBeCalled();
21+
22+
unmount();
23+
expect(callback).toBeCalledTimes(1);
24+
});
25+
26+
it("should ensure the callback function doesn't have a stale closure", () => {
27+
const callback = jest.fn();
28+
function Test() {
29+
const [value, setValue] = useState("");
30+
31+
useOnUnmount(() => {
32+
callback(value);
33+
});
34+
35+
return (
36+
<input
37+
type="text"
38+
value={value}
39+
onChange={(event) => setValue(event.currentTarget.value)}
40+
/>
41+
);
42+
}
43+
44+
const { getByRole, unmount } = render(<Test />);
45+
const input = getByRole("textbox");
46+
47+
userEvent.type(input, "my new value");
48+
expect(callback).not.toBeCalled();
49+
50+
unmount();
51+
expect(callback).toBeCalledWith("my new value");
52+
});
53+
});

packages/utils/src/index.ts

+21-25
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,31 @@
1+
export * from "./applyRef";
2+
export * from "./bem";
13
export * from "./colors";
4+
export * from "./containsElement";
5+
export * from "./defaults";
6+
export * from "./Dir";
27
export * from "./events";
8+
export * from "./getPercentage";
39
export * from "./layout";
10+
export * from "./loop";
411
export * from "./mode";
5-
export * from "./search";
6-
export * from "./sizing";
7-
export * from "./positioning";
8-
export * from "./wia-aria";
9-
10-
export * from "./Dir";
11-
12-
export * from "./bem";
12+
export * from "./nearest";
1313
export * from "./omit";
14-
export * from "./defaults";
14+
export * from "./positioning";
1515
export * from "./scrollIntoView";
16-
export * from "./containsElement";
17-
18-
export * from "./unitToNumber";
19-
export * from "./applyRef";
16+
export * from "./search";
17+
export * from "./sizing";
2018
export * from "./throttle";
21-
export * from "./loop";
22-
export * from "./getPercentage";
23-
export * from "./nearest";
24-
export * from "./withinRange";
25-
19+
export * from "./types";
20+
export * from "./unitToNumber";
21+
export * from "./useCloseOnOutsideClick";
2622
export * from "./useEnsuredRef";
27-
export * from "./useIsomorphicLayoutEffect";
28-
export * from "./useToggle";
2923
export * from "./useInterval";
30-
export * from "./useTimeout";
31-
export * from "./useTempValue";
24+
export * from "./useIsomorphicLayoutEffect";
25+
export * from "./useOnUnmount";
3226
export * from "./useRefCache";
33-
export * from "./useCloseOnOutsideClick";
34-
35-
export * from "./types";
27+
export * from "./useTempValue";
28+
export * from "./useTimeout";
29+
export * from "./useToggle";
30+
export * from "./wia-aria";
31+
export * from "./withinRange";

packages/utils/src/useOnUnmount.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useEffect, useRef } from "react";
2+
3+
/**
4+
* A simple hook that only triggers the callback when a component is unmounted.
5+
* This will make sure that the callback function does not have a stale closure
6+
* by the time the component unmounts as well.
7+
*
8+
* @example
9+
* Simple Example
10+
* ```ts
11+
* useOnUnmount(() => {
12+
* console.log('Component is unmounted.');
13+
* });
14+
*
15+
* const [data, setData] = useState(initialData);
16+
* useOnUnmount(() => {
17+
* API.saveCurrentData(data);
18+
* });
19+
*
20+
* // update data
21+
* ```
22+
*
23+
* @param callback - the function to call when the component unmounts.
24+
*/
25+
export function useOnUnmount(callback: () => void): void {
26+
const ref = useRef(callback);
27+
useEffect(() => {
28+
ref.current = callback;
29+
});
30+
31+
return useEffect(() => () => ref.current(), []);
32+
}

0 commit comments

Comments
 (0)