Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | 53x 53x 53x 53x 44x 6x 38x 38x 38x 3x | import { useEffect, useRef } from "react";
/**
* Options for useUnmount hook
*/
export interface UseUnmountOptions {
/**
* Whether the unmount callback is enabled.
* When false, the callback will not be executed on unmount.
* @default true
*/
enabled?: boolean;
}
/**
* Executes a callback function when the component unmounts.
* The callback always has access to the latest values (closure freshness).
*
* Features:
* - Closure freshness: callback always sees latest state/props
* - Error handling: errors in callback don't break unmount
* - Conditional execution via `enabled` option
* - SSR compatible
* - TypeScript support
*
* @param callback - The function to execute on unmount
* @param options - Configuration options
*
* @example
* ```tsx
* // Basic usage
* function MyComponent() {
* useUnmount(() => {
* console.log("Component unmounted");
* });
*
* return <div>Hello</div>;
* }
* ```
*
* @example
* ```tsx
* // With latest state access
* function FormComponent() {
* const [formData, setFormData] = useState({});
*
* useUnmount(() => {
* // formData will have the latest value at unmount time
* saveToLocalStorage(formData);
* });
*
* return <form>...</form>;
* }
* ```
*
* @example
* ```tsx
* // Conditional cleanup
* function TrackingComponent({ trackingEnabled }) {
* useUnmount(
* () => {
* sendAnalyticsEvent("component_unmounted");
* },
* { enabled: trackingEnabled }
* );
*
* return <div>Tracked content</div>;
* }
* ```
*
* @example
* ```tsx
* // Resource cleanup
* function WebSocketComponent() {
* const wsRef = useRef<WebSocket | null>(null);
*
* useEffect(() => {
* wsRef.current = new WebSocket("wss://example.com");
* }, []);
*
* useUnmount(() => {
* wsRef.current?.close();
* });
*
* return <div>Connected</div>;
* }
* ```
*
* @remarks
* **React StrictMode**: In development with StrictMode, React intentionally
* mounts, unmounts, and remounts components to detect side effects. This means
* the unmount callback may be called multiple times during development.
* This is expected behavior and helps identify issues with cleanup logic.
*
* **Error Handling**: If the callback throws an error, it will be caught and
* logged to the console. This prevents unmount errors from breaking
* the entire component tree unmount process.
*/
export function useUnmount(
callback: () => void,
options: UseUnmountOptions = {}
): void {
const { enabled = true } = options;
// Store callback in ref to always have the latest version
// This ensures closure freshness - the callback at unmount time
// will have access to the most recent state/props values
const callbackRef = useRef(callback);
// Update the ref on every render to capture latest callback
callbackRef.current = callback;
useEffect(() => {
// If disabled, don't set up cleanup
if (!enabled) {
return;
}
// Return cleanup function
return () => {
try {
callbackRef.current();
} catch (error) {
// Log the error to help with debugging
// Catch to prevent breaking other unmounts in the component tree
console.error("useUnmount: Error in unmount callback:", error);
}
};
}, [enabled]);
}
|