@malconlobo/react-utility-hooks 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/index.d.mts +82 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +225 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +191 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# react-utility-hooks
|
|
2
|
+
|
|
3
|
+
A collection of robust, type-safe utility React hooks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install react-utility-hooks
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Hooks Available
|
|
12
|
+
|
|
13
|
+
- `useDebounce`: Delays updating a value.
|
|
14
|
+
- `useThrottle`: Limits how often a value updates.
|
|
15
|
+
- `usePrevious`: Tracks the previous state or prop value.
|
|
16
|
+
- `useLocalStorage`: Syncs state with local storage.
|
|
17
|
+
- `useMediaQuery`: Tracks the state of a CSS media query.
|
|
18
|
+
- `useAsync`: Handles asynchronous operations and exposes status/error.
|
|
19
|
+
- `useIntersectionObserver`: Tracks element visibility in the viewport.
|
|
20
|
+
- `useInfiniteScroll`: Triggers callbacks when scrolling near the bottom of a container.
|
|
21
|
+
|
|
22
|
+
## Usage Examples
|
|
23
|
+
|
|
24
|
+
### `useDebounce`
|
|
25
|
+
```tsx
|
|
26
|
+
import { useState } from 'react';
|
|
27
|
+
import { useDebounce } from 'react-utility-hooks';
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
31
|
+
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<input
|
|
35
|
+
value={searchTerm}
|
|
36
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `useLocalStorage`
|
|
43
|
+
```tsx
|
|
44
|
+
import { useLocalStorage } from 'react-utility-hooks';
|
|
45
|
+
|
|
46
|
+
function App() {
|
|
47
|
+
const [theme, setTheme] = useLocalStorage('theme', 'light');
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
|
|
51
|
+
Toggle Theme
|
|
52
|
+
</button>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { RefObject } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Delays updating the value until after `delay` milliseconds have elapsed.
|
|
6
|
+
*
|
|
7
|
+
* @param value The value to debounce.
|
|
8
|
+
* @param delay The delay in milliseconds.
|
|
9
|
+
* @returns The debounced value.
|
|
10
|
+
*/
|
|
11
|
+
declare function useDebounce<T>(value: T, delay: number): T;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns a throttled version of the value that only updates at most once every `delay` milliseconds.
|
|
15
|
+
*
|
|
16
|
+
* @param value The value to throttle.
|
|
17
|
+
* @param delay The throttle limit in milliseconds.
|
|
18
|
+
* @returns The throttled value.
|
|
19
|
+
*/
|
|
20
|
+
declare function useThrottle<T>(value: T, delay: number): T;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Stores the previous state or prop value.
|
|
24
|
+
*
|
|
25
|
+
* @param value The value to track.
|
|
26
|
+
* @returns The previous value.
|
|
27
|
+
*/
|
|
28
|
+
declare function usePrevious<T>(value: T): T | undefined;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Syncs state to local storage so that it persists through a page refresh.
|
|
32
|
+
*
|
|
33
|
+
* @param key The local storage key.
|
|
34
|
+
* @param initialValue The initial value.
|
|
35
|
+
* @returns A tuple containing the state value and the setter function.
|
|
36
|
+
*/
|
|
37
|
+
declare function useLocalStorage<T>(key: string, initialValue: T): readonly [T, (value: T | ((val: T) => T)) => void];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Tracks the state of a CSS media query.
|
|
41
|
+
*
|
|
42
|
+
* @param query The media query to track.
|
|
43
|
+
* @returns True if the media query matches, false otherwise.
|
|
44
|
+
*/
|
|
45
|
+
declare function useMediaQuery(query: string): boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handles asynchronous operations (promises) providing state.
|
|
49
|
+
*
|
|
50
|
+
* @param asyncFunction The async function to execute.
|
|
51
|
+
* @param immediate Whether to execute the function immediately.
|
|
52
|
+
* @returns The state and execute function.
|
|
53
|
+
*/
|
|
54
|
+
declare function useAsync<T>(asyncFunction: () => Promise<T>, immediate?: boolean): {
|
|
55
|
+
execute: () => Promise<void | T>;
|
|
56
|
+
status: "idle" | "pending" | "success" | "error";
|
|
57
|
+
value: T | null;
|
|
58
|
+
error: Error | null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
interface Args extends IntersectionObserverInit {
|
|
62
|
+
freezeOnceVisible?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Tracks the intersection of a target element with an ancestor element or with a top-level document's viewport.
|
|
66
|
+
*
|
|
67
|
+
* @param elementRef The ref of the element to observe.
|
|
68
|
+
* @param options Observer options and freezeOnceVisible.
|
|
69
|
+
* @returns The IntersectionObserverEntry.
|
|
70
|
+
*/
|
|
71
|
+
declare function useIntersectionObserver(elementRef: RefObject<Element>, { threshold, root, rootMargin, freezeOnceVisible }: Args): IntersectionObserverEntry | undefined;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Uses useIntersectionObserver to trigger a callback when the user scrolls near the bottom of a container.
|
|
75
|
+
*
|
|
76
|
+
* @param callback The callback to execute when intersecting.
|
|
77
|
+
* @param options Intersection options.
|
|
78
|
+
* @returns A ref to attach to the target element.
|
|
79
|
+
*/
|
|
80
|
+
declare function useInfiniteScroll<T extends HTMLElement>(callback: () => void, options?: IntersectionObserverInit): react.MutableRefObject<T | null>;
|
|
81
|
+
|
|
82
|
+
export { useAsync, useDebounce, useInfiniteScroll, useIntersectionObserver, useLocalStorage, useMediaQuery, usePrevious, useThrottle };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { RefObject } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Delays updating the value until after `delay` milliseconds have elapsed.
|
|
6
|
+
*
|
|
7
|
+
* @param value The value to debounce.
|
|
8
|
+
* @param delay The delay in milliseconds.
|
|
9
|
+
* @returns The debounced value.
|
|
10
|
+
*/
|
|
11
|
+
declare function useDebounce<T>(value: T, delay: number): T;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns a throttled version of the value that only updates at most once every `delay` milliseconds.
|
|
15
|
+
*
|
|
16
|
+
* @param value The value to throttle.
|
|
17
|
+
* @param delay The throttle limit in milliseconds.
|
|
18
|
+
* @returns The throttled value.
|
|
19
|
+
*/
|
|
20
|
+
declare function useThrottle<T>(value: T, delay: number): T;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Stores the previous state or prop value.
|
|
24
|
+
*
|
|
25
|
+
* @param value The value to track.
|
|
26
|
+
* @returns The previous value.
|
|
27
|
+
*/
|
|
28
|
+
declare function usePrevious<T>(value: T): T | undefined;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Syncs state to local storage so that it persists through a page refresh.
|
|
32
|
+
*
|
|
33
|
+
* @param key The local storage key.
|
|
34
|
+
* @param initialValue The initial value.
|
|
35
|
+
* @returns A tuple containing the state value and the setter function.
|
|
36
|
+
*/
|
|
37
|
+
declare function useLocalStorage<T>(key: string, initialValue: T): readonly [T, (value: T | ((val: T) => T)) => void];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Tracks the state of a CSS media query.
|
|
41
|
+
*
|
|
42
|
+
* @param query The media query to track.
|
|
43
|
+
* @returns True if the media query matches, false otherwise.
|
|
44
|
+
*/
|
|
45
|
+
declare function useMediaQuery(query: string): boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handles asynchronous operations (promises) providing state.
|
|
49
|
+
*
|
|
50
|
+
* @param asyncFunction The async function to execute.
|
|
51
|
+
* @param immediate Whether to execute the function immediately.
|
|
52
|
+
* @returns The state and execute function.
|
|
53
|
+
*/
|
|
54
|
+
declare function useAsync<T>(asyncFunction: () => Promise<T>, immediate?: boolean): {
|
|
55
|
+
execute: () => Promise<void | T>;
|
|
56
|
+
status: "idle" | "pending" | "success" | "error";
|
|
57
|
+
value: T | null;
|
|
58
|
+
error: Error | null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
interface Args extends IntersectionObserverInit {
|
|
62
|
+
freezeOnceVisible?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Tracks the intersection of a target element with an ancestor element or with a top-level document's viewport.
|
|
66
|
+
*
|
|
67
|
+
* @param elementRef The ref of the element to observe.
|
|
68
|
+
* @param options Observer options and freezeOnceVisible.
|
|
69
|
+
* @returns The IntersectionObserverEntry.
|
|
70
|
+
*/
|
|
71
|
+
declare function useIntersectionObserver(elementRef: RefObject<Element>, { threshold, root, rootMargin, freezeOnceVisible }: Args): IntersectionObserverEntry | undefined;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Uses useIntersectionObserver to trigger a callback when the user scrolls near the bottom of a container.
|
|
75
|
+
*
|
|
76
|
+
* @param callback The callback to execute when intersecting.
|
|
77
|
+
* @param options Intersection options.
|
|
78
|
+
* @returns A ref to attach to the target element.
|
|
79
|
+
*/
|
|
80
|
+
declare function useInfiniteScroll<T extends HTMLElement>(callback: () => void, options?: IntersectionObserverInit): react.MutableRefObject<T | null>;
|
|
81
|
+
|
|
82
|
+
export { useAsync, useDebounce, useInfiniteScroll, useIntersectionObserver, useLocalStorage, useMediaQuery, usePrevious, useThrottle };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
useAsync: () => useAsync,
|
|
24
|
+
useDebounce: () => useDebounce,
|
|
25
|
+
useInfiniteScroll: () => useInfiniteScroll,
|
|
26
|
+
useIntersectionObserver: () => useIntersectionObserver,
|
|
27
|
+
useLocalStorage: () => useLocalStorage,
|
|
28
|
+
useMediaQuery: () => useMediaQuery,
|
|
29
|
+
usePrevious: () => usePrevious,
|
|
30
|
+
useThrottle: () => useThrottle
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(src_exports);
|
|
33
|
+
|
|
34
|
+
// src/hooks/useDebounce.ts
|
|
35
|
+
var import_react = require("react");
|
|
36
|
+
function useDebounce(value, delay) {
|
|
37
|
+
const [debouncedValue, setDebouncedValue] = (0, import_react.useState)(value);
|
|
38
|
+
(0, import_react.useEffect)(() => {
|
|
39
|
+
const handler = setTimeout(() => {
|
|
40
|
+
setDebouncedValue(value);
|
|
41
|
+
}, delay);
|
|
42
|
+
return () => {
|
|
43
|
+
clearTimeout(handler);
|
|
44
|
+
};
|
|
45
|
+
}, [value, delay]);
|
|
46
|
+
return debouncedValue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/hooks/useThrottle.ts
|
|
50
|
+
var import_react2 = require("react");
|
|
51
|
+
function useThrottle(value, delay) {
|
|
52
|
+
const [throttledValue, setThrottledValue] = (0, import_react2.useState)(value);
|
|
53
|
+
const lastExecuted = (0, import_react2.useRef)(Date.now());
|
|
54
|
+
(0, import_react2.useEffect)(() => {
|
|
55
|
+
const timeSinceLastExecution = Date.now() - lastExecuted.current;
|
|
56
|
+
if (timeSinceLastExecution >= delay) {
|
|
57
|
+
setThrottledValue(value);
|
|
58
|
+
lastExecuted.current = Date.now();
|
|
59
|
+
} else {
|
|
60
|
+
const timerId = setTimeout(() => {
|
|
61
|
+
setThrottledValue(value);
|
|
62
|
+
lastExecuted.current = Date.now();
|
|
63
|
+
}, delay - timeSinceLastExecution);
|
|
64
|
+
return () => clearTimeout(timerId);
|
|
65
|
+
}
|
|
66
|
+
}, [value, delay]);
|
|
67
|
+
return throttledValue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/hooks/usePrevious.ts
|
|
71
|
+
var import_react3 = require("react");
|
|
72
|
+
function usePrevious(value) {
|
|
73
|
+
const ref = (0, import_react3.useRef)();
|
|
74
|
+
(0, import_react3.useEffect)(() => {
|
|
75
|
+
ref.current = value;
|
|
76
|
+
}, [value]);
|
|
77
|
+
return ref.current;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/hooks/useLocalStorage.ts
|
|
81
|
+
var import_react4 = require("react");
|
|
82
|
+
function useLocalStorage(key, initialValue) {
|
|
83
|
+
const [storedValue, setStoredValue] = (0, import_react4.useState)(() => {
|
|
84
|
+
try {
|
|
85
|
+
if (typeof window === "undefined") {
|
|
86
|
+
return initialValue;
|
|
87
|
+
}
|
|
88
|
+
const item = window.localStorage.getItem(key);
|
|
89
|
+
return item ? JSON.parse(item) : initialValue;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.warn(`Error reading localStorage key "${key}":`, error);
|
|
92
|
+
return initialValue;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const setValue = (value) => {
|
|
96
|
+
try {
|
|
97
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
98
|
+
setStoredValue(valueToStore);
|
|
99
|
+
if (typeof window !== "undefined") {
|
|
100
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.warn(`Error setting localStorage key "${key}":`, error);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
return [storedValue, setValue];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/hooks/useMediaQuery.ts
|
|
110
|
+
var import_react5 = require("react");
|
|
111
|
+
function useMediaQuery(query) {
|
|
112
|
+
const getMatches = (query2) => {
|
|
113
|
+
if (typeof window !== "undefined") {
|
|
114
|
+
return window.matchMedia(query2).matches;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
};
|
|
118
|
+
const [matches, setMatches] = (0, import_react5.useState)(() => getMatches(query));
|
|
119
|
+
(0, import_react5.useEffect)(() => {
|
|
120
|
+
function handleChange() {
|
|
121
|
+
setMatches(getMatches(query));
|
|
122
|
+
}
|
|
123
|
+
const matchMedia = window.matchMedia(query);
|
|
124
|
+
handleChange();
|
|
125
|
+
if (matchMedia.addListener) {
|
|
126
|
+
matchMedia.addListener(handleChange);
|
|
127
|
+
} else {
|
|
128
|
+
matchMedia.addEventListener("change", handleChange);
|
|
129
|
+
}
|
|
130
|
+
return () => {
|
|
131
|
+
if (matchMedia.removeListener) {
|
|
132
|
+
matchMedia.removeListener(handleChange);
|
|
133
|
+
} else {
|
|
134
|
+
matchMedia.removeEventListener("change", handleChange);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}, [query]);
|
|
138
|
+
return matches;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/hooks/useAsync.ts
|
|
142
|
+
var import_react6 = require("react");
|
|
143
|
+
function useAsync(asyncFunction, immediate = true) {
|
|
144
|
+
const [state, setState] = (0, import_react6.useState)({
|
|
145
|
+
status: "idle",
|
|
146
|
+
value: null,
|
|
147
|
+
error: null
|
|
148
|
+
});
|
|
149
|
+
const execute = (0, import_react6.useCallback)(() => {
|
|
150
|
+
setState({ status: "pending", value: null, error: null });
|
|
151
|
+
return asyncFunction().then((response) => {
|
|
152
|
+
setState({ status: "success", value: response, error: null });
|
|
153
|
+
return response;
|
|
154
|
+
}).catch((error) => {
|
|
155
|
+
setState({ status: "error", value: null, error });
|
|
156
|
+
});
|
|
157
|
+
}, [asyncFunction]);
|
|
158
|
+
(0, import_react6.useEffect)(() => {
|
|
159
|
+
if (immediate) {
|
|
160
|
+
execute();
|
|
161
|
+
}
|
|
162
|
+
}, [execute, immediate]);
|
|
163
|
+
return { ...state, execute };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/hooks/useIntersectionObserver.ts
|
|
167
|
+
var import_react7 = require("react");
|
|
168
|
+
function useIntersectionObserver(elementRef, { threshold = 0, root = null, rootMargin = "0%", freezeOnceVisible = false }) {
|
|
169
|
+
const [entry, setEntry] = (0, import_react7.useState)();
|
|
170
|
+
const frozen = entry?.isIntersecting && freezeOnceVisible;
|
|
171
|
+
const updateEntry = ([entry2]) => {
|
|
172
|
+
setEntry(entry2);
|
|
173
|
+
};
|
|
174
|
+
(0, import_react7.useEffect)(() => {
|
|
175
|
+
const node = elementRef?.current;
|
|
176
|
+
const hasIOSupport = !!window.IntersectionObserver;
|
|
177
|
+
if (!hasIOSupport || frozen || !node)
|
|
178
|
+
return;
|
|
179
|
+
const observerParams = { threshold, root, rootMargin };
|
|
180
|
+
const observer = new IntersectionObserver(updateEntry, observerParams);
|
|
181
|
+
observer.observe(node);
|
|
182
|
+
return () => observer.disconnect();
|
|
183
|
+
}, [elementRef, threshold, root, rootMargin, frozen]);
|
|
184
|
+
return entry;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/hooks/useInfiniteScroll.ts
|
|
188
|
+
var import_react8 = require("react");
|
|
189
|
+
function useInfiniteScroll(callback, options = { rootMargin: "100px" }) {
|
|
190
|
+
const observerRef = (0, import_react8.useRef)(null);
|
|
191
|
+
const targetRef = (0, import_react8.useRef)(null);
|
|
192
|
+
const handleIntersect = (0, import_react8.useCallback)(
|
|
193
|
+
(entries) => {
|
|
194
|
+
const [entry] = entries;
|
|
195
|
+
if (entry.isIntersecting) {
|
|
196
|
+
callback();
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
[callback]
|
|
200
|
+
);
|
|
201
|
+
(0, import_react8.useEffect)(() => {
|
|
202
|
+
if (!targetRef.current)
|
|
203
|
+
return;
|
|
204
|
+
observerRef.current = new IntersectionObserver(handleIntersect, options);
|
|
205
|
+
observerRef.current.observe(targetRef.current);
|
|
206
|
+
return () => {
|
|
207
|
+
if (observerRef.current) {
|
|
208
|
+
observerRef.current.disconnect();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}, [handleIntersect, options.root, options.rootMargin, options.threshold]);
|
|
212
|
+
return targetRef;
|
|
213
|
+
}
|
|
214
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
215
|
+
0 && (module.exports = {
|
|
216
|
+
useAsync,
|
|
217
|
+
useDebounce,
|
|
218
|
+
useInfiniteScroll,
|
|
219
|
+
useIntersectionObserver,
|
|
220
|
+
useLocalStorage,
|
|
221
|
+
useMediaQuery,
|
|
222
|
+
usePrevious,
|
|
223
|
+
useThrottle
|
|
224
|
+
});
|
|
225
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/hooks/useDebounce.ts","../src/hooks/useThrottle.ts","../src/hooks/usePrevious.ts","../src/hooks/useLocalStorage.ts","../src/hooks/useMediaQuery.ts","../src/hooks/useAsync.ts","../src/hooks/useIntersectionObserver.ts","../src/hooks/useInfiniteScroll.ts"],"sourcesContent":["export * from './hooks/useDebounce';\nexport * from './hooks/useThrottle';\nexport * from './hooks/usePrevious';\nexport * from './hooks/useLocalStorage';\nexport * from './hooks/useMediaQuery';\nexport * from './hooks/useAsync';\nexport * from './hooks/useIntersectionObserver';\nexport * from './hooks/useInfiniteScroll';\n","import { useState, useEffect } from 'react';\n\n/**\n * Delays updating the value until after `delay` milliseconds have elapsed.\n *\n * @param value The value to debounce.\n * @param delay The delay in milliseconds.\n * @returns The debounced value.\n */\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(handler);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n","import { useState, useEffect, useRef } from 'react';\n\n/**\n * Returns a throttled version of the value that only updates at most once every `delay` milliseconds.\n *\n * @param value The value to throttle.\n * @param delay The throttle limit in milliseconds.\n * @returns The throttled value.\n */\nexport function useThrottle<T>(value: T, delay: number): T {\n const [throttledValue, setThrottledValue] = useState<T>(value);\n const lastExecuted = useRef<number>(Date.now());\n\n useEffect(() => {\n const timeSinceLastExecution = Date.now() - lastExecuted.current;\n\n if (timeSinceLastExecution >= delay) {\n setThrottledValue(value);\n lastExecuted.current = Date.now();\n } else {\n const timerId = setTimeout(() => {\n setThrottledValue(value);\n lastExecuted.current = Date.now();\n }, delay - timeSinceLastExecution);\n\n return () => clearTimeout(timerId);\n }\n }, [value, delay]);\n\n return throttledValue;\n}\n","import { useRef, useEffect } from 'react';\n\n/**\n * Stores the previous state or prop value.\n *\n * @param value The value to track.\n * @returns The previous value.\n */\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>();\n\n useEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref.current;\n}\n","import { useState } from 'react';\n\n/**\n * Syncs state to local storage so that it persists through a page refresh.\n *\n * @param key The local storage key.\n * @param initialValue The initial value.\n * @returns A tuple containing the state value and the setter function.\n */\nexport function useLocalStorage<T>(key: string, initialValue: T) {\n const [storedValue, setStoredValue] = useState<T>(() => {\n try {\n if (typeof window === 'undefined') {\n return initialValue;\n }\n const item = window.localStorage.getItem(key);\n return item ? JSON.parse(item) : initialValue;\n } catch (error) {\n console.warn(`Error reading localStorage key \"${key}\":`, error);\n return initialValue;\n }\n });\n\n const setValue = (value: T | ((val: T) => T)) => {\n try {\n const valueToStore = value instanceof Function ? value(storedValue) : value;\n setStoredValue(valueToStore);\n if (typeof window !== 'undefined') {\n window.localStorage.setItem(key, JSON.stringify(valueToStore));\n }\n } catch (error) {\n console.warn(`Error setting localStorage key \"${key}\":`, error);\n }\n };\n\n return [storedValue, setValue] as const;\n}\n","import { useState, useEffect } from 'react';\n\n/**\n * Tracks the state of a CSS media query.\n *\n * @param query The media query to track.\n * @returns True if the media query matches, false otherwise.\n */\nexport function useMediaQuery(query: string): boolean {\n const getMatches = (query: string): boolean => {\n if (typeof window !== 'undefined') {\n return window.matchMedia(query).matches;\n }\n return false;\n };\n\n const [matches, setMatches] = useState<boolean>(() => getMatches(query));\n\n useEffect(() => {\n function handleChange() {\n setMatches(getMatches(query));\n }\n\n const matchMedia = window.matchMedia(query);\n handleChange();\n\n if (matchMedia.addListener) {\n matchMedia.addListener(handleChange);\n } else {\n matchMedia.addEventListener('change', handleChange);\n }\n\n return () => {\n if (matchMedia.removeListener) {\n matchMedia.removeListener(handleChange);\n } else {\n matchMedia.removeEventListener('change', handleChange);\n }\n };\n }, [query]);\n\n return matches;\n}\n","import { useState, useCallback, useEffect } from 'react';\n\ntype AsyncState<T> = {\n status: 'idle' | 'pending' | 'success' | 'error';\n value: T | null;\n error: Error | null;\n};\n\n/**\n * Handles asynchronous operations (promises) providing state.\n *\n * @param asyncFunction The async function to execute.\n * @param immediate Whether to execute the function immediately.\n * @returns The state and execute function.\n */\nexport function useAsync<T>(\n asyncFunction: () => Promise<T>,\n immediate = true\n) {\n const [state, setState] = useState<AsyncState<T>>({\n status: 'idle',\n value: null,\n error: null,\n });\n\n const execute = useCallback(() => {\n setState({ status: 'pending', value: null, error: null });\n\n return asyncFunction()\n .then((response) => {\n setState({ status: 'success', value: response, error: null });\n return response;\n })\n .catch((error) => {\n setState({ status: 'error', value: null, error });\n });\n }, [asyncFunction]);\n\n useEffect(() => {\n if (immediate) {\n execute();\n }\n }, [execute, immediate]);\n\n return { ...state, execute };\n}\n","import { useEffect, useState, RefObject } from 'react';\n\ninterface Args extends IntersectionObserverInit {\n freezeOnceVisible?: boolean;\n}\n\n/**\n * Tracks the intersection of a target element with an ancestor element or with a top-level document's viewport.\n *\n * @param elementRef The ref of the element to observe.\n * @param options Observer options and freezeOnceVisible.\n * @returns The IntersectionObserverEntry.\n */\nexport function useIntersectionObserver(\n elementRef: RefObject<Element>,\n { threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false }: Args\n): IntersectionObserverEntry | undefined {\n const [entry, setEntry] = useState<IntersectionObserverEntry>();\n\n const frozen = entry?.isIntersecting && freezeOnceVisible;\n\n const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {\n setEntry(entry);\n };\n\n useEffect(() => {\n const node = elementRef?.current;\n const hasIOSupport = !!window.IntersectionObserver;\n\n if (!hasIOSupport || frozen || !node) return;\n\n const observerParams = { threshold, root, rootMargin };\n const observer = new IntersectionObserver(updateEntry, observerParams);\n\n observer.observe(node);\n\n return () => observer.disconnect();\n }, [elementRef, threshold, root, rootMargin, frozen]);\n\n return entry;\n}\n","import { useEffect, useRef, useCallback } from 'react';\n\n/**\n * Uses useIntersectionObserver to trigger a callback when the user scrolls near the bottom of a container.\n *\n * @param callback The callback to execute when intersecting.\n * @param options Intersection options.\n * @returns A ref to attach to the target element.\n */\nexport function useInfiniteScroll<T extends HTMLElement>(\n callback: () => void,\n options: IntersectionObserverInit = { rootMargin: '100px' }\n) {\n const observerRef = useRef<IntersectionObserver | null>(null);\n const targetRef = useRef<T | null>(null);\n\n const handleIntersect = useCallback(\n (entries: IntersectionObserverEntry[]) => {\n const [entry] = entries;\n if (entry.isIntersecting) {\n callback();\n }\n },\n [callback]\n );\n\n useEffect(() => {\n if (!targetRef.current) return;\n\n observerRef.current = new IntersectionObserver(handleIntersect, options);\n observerRef.current.observe(targetRef.current);\n\n return () => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n };\n }, [handleIntersect, options.root, options.rootMargin, options.threshold]);\n\n return targetRef;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAoC;AAS7B,SAAS,YAAe,OAAU,OAAkB;AACzD,QAAM,CAAC,gBAAgB,iBAAiB,QAAI,uBAAY,KAAK;AAE7D,8BAAU,MAAM;AACd,UAAM,UAAU,WAAW,MAAM;AAC/B,wBAAkB,KAAK;AAAA,IACzB,GAAG,KAAK;AAER,WAAO,MAAM;AACX,mBAAa,OAAO;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,OAAO,KAAK,CAAC;AAEjB,SAAO;AACT;;;ACvBA,IAAAA,gBAA4C;AASrC,SAAS,YAAe,OAAU,OAAkB;AACzD,QAAM,CAAC,gBAAgB,iBAAiB,QAAI,wBAAY,KAAK;AAC7D,QAAM,mBAAe,sBAAe,KAAK,IAAI,CAAC;AAE9C,+BAAU,MAAM;AACd,UAAM,yBAAyB,KAAK,IAAI,IAAI,aAAa;AAEzD,QAAI,0BAA0B,OAAO;AACnC,wBAAkB,KAAK;AACvB,mBAAa,UAAU,KAAK,IAAI;AAAA,IAClC,OAAO;AACL,YAAM,UAAU,WAAW,MAAM;AAC/B,0BAAkB,KAAK;AACvB,qBAAa,UAAU,KAAK,IAAI;AAAA,MAClC,GAAG,QAAQ,sBAAsB;AAEjC,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,EACF,GAAG,CAAC,OAAO,KAAK,CAAC;AAEjB,SAAO;AACT;;;AC9BA,IAAAC,gBAAkC;AAQ3B,SAAS,YAAe,OAAyB;AACtD,QAAM,UAAM,sBAAU;AAEtB,+BAAU,MAAM;AACd,QAAI,UAAU;AAAA,EAChB,GAAG,CAAC,KAAK,CAAC;AAEV,SAAO,IAAI;AACb;;;AChBA,IAAAC,gBAAyB;AASlB,SAAS,gBAAmB,KAAa,cAAiB;AAC/D,QAAM,CAAC,aAAa,cAAc,QAAI,wBAAY,MAAM;AACtD,QAAI;AACF,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO;AAAA,MACT;AACA,YAAM,OAAO,OAAO,aAAa,QAAQ,GAAG;AAC5C,aAAO,OAAO,KAAK,MAAM,IAAI,IAAI;AAAA,IACnC,SAAS,OAAO;AACd,cAAQ,KAAK,mCAAmC,GAAG,MAAM,KAAK;AAC9D,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AAED,QAAM,WAAW,CAAC,UAA+B;AAC/C,QAAI;AACF,YAAM,eAAe,iBAAiB,WAAW,MAAM,WAAW,IAAI;AACtE,qBAAe,YAAY;AAC3B,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,aAAa,QAAQ,KAAK,KAAK,UAAU,YAAY,CAAC;AAAA,MAC/D;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,KAAK,mCAAmC,GAAG,MAAM,KAAK;AAAA,IAChE;AAAA,EACF;AAEA,SAAO,CAAC,aAAa,QAAQ;AAC/B;;;ACpCA,IAAAC,gBAAoC;AAQ7B,SAAS,cAAc,OAAwB;AACpD,QAAM,aAAa,CAACC,WAA2B;AAC7C,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,OAAO,WAAWA,MAAK,EAAE;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAEA,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAkB,MAAM,WAAW,KAAK,CAAC;AAEvE,+BAAU,MAAM;AACd,aAAS,eAAe;AACtB,iBAAW,WAAW,KAAK,CAAC;AAAA,IAC9B;AAEA,UAAM,aAAa,OAAO,WAAW,KAAK;AAC1C,iBAAa;AAEb,QAAI,WAAW,aAAa;AAC1B,iBAAW,YAAY,YAAY;AAAA,IACrC,OAAO;AACL,iBAAW,iBAAiB,UAAU,YAAY;AAAA,IACpD;AAEA,WAAO,MAAM;AACX,UAAI,WAAW,gBAAgB;AAC7B,mBAAW,eAAe,YAAY;AAAA,MACxC,OAAO;AACL,mBAAW,oBAAoB,UAAU,YAAY;AAAA,MACvD;AAAA,IACF;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,SAAO;AACT;;;AC1CA,IAAAC,gBAAiD;AAe1C,SAAS,SACd,eACA,YAAY,MACZ;AACA,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAwB;AAAA,IAChD,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,OAAO;AAAA,EACT,CAAC;AAED,QAAM,cAAU,2BAAY,MAAM;AAChC,aAAS,EAAE,QAAQ,WAAW,OAAO,MAAM,OAAO,KAAK,CAAC;AAExD,WAAO,cAAc,EAClB,KAAK,CAAC,aAAa;AAClB,eAAS,EAAE,QAAQ,WAAW,OAAO,UAAU,OAAO,KAAK,CAAC;AAC5D,aAAO;AAAA,IACT,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,eAAS,EAAE,QAAQ,SAAS,OAAO,MAAM,MAAM,CAAC;AAAA,IAClD,CAAC;AAAA,EACL,GAAG,CAAC,aAAa,CAAC;AAElB,+BAAU,MAAM;AACd,QAAI,WAAW;AACb,cAAQ;AAAA,IACV;AAAA,EACF,GAAG,CAAC,SAAS,SAAS,CAAC;AAEvB,SAAO,EAAE,GAAG,OAAO,QAAQ;AAC7B;;;AC7CA,IAAAC,gBAA+C;AAaxC,SAAS,wBACd,YACA,EAAE,YAAY,GAAG,OAAO,MAAM,aAAa,MAAM,oBAAoB,MAAM,GACpC;AACvC,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAoC;AAE9D,QAAM,SAAS,OAAO,kBAAkB;AAExC,QAAM,cAAc,CAAC,CAACC,MAAK,MAAyC;AAClE,aAASA,MAAK;AAAA,EAChB;AAEA,+BAAU,MAAM;AACd,UAAM,OAAO,YAAY;AACzB,UAAM,eAAe,CAAC,CAAC,OAAO;AAE9B,QAAI,CAAC,gBAAgB,UAAU,CAAC;AAAM;AAEtC,UAAM,iBAAiB,EAAE,WAAW,MAAM,WAAW;AACrD,UAAM,WAAW,IAAI,qBAAqB,aAAa,cAAc;AAErE,aAAS,QAAQ,IAAI;AAErB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,YAAY,WAAW,MAAM,YAAY,MAAM,CAAC;AAEpD,SAAO;AACT;;;ACxCA,IAAAC,gBAA+C;AASxC,SAAS,kBACd,UACA,UAAoC,EAAE,YAAY,QAAQ,GAC1D;AACA,QAAM,kBAAc,sBAAoC,IAAI;AAC5D,QAAM,gBAAY,sBAAiB,IAAI;AAEvC,QAAM,sBAAkB;AAAA,IACtB,CAAC,YAAyC;AACxC,YAAM,CAAC,KAAK,IAAI;AAChB,UAAI,MAAM,gBAAgB;AACxB,iBAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,+BAAU,MAAM;AACd,QAAI,CAAC,UAAU;AAAS;AAExB,gBAAY,UAAU,IAAI,qBAAqB,iBAAiB,OAAO;AACvE,gBAAY,QAAQ,QAAQ,UAAU,OAAO;AAE7C,WAAO,MAAM;AACX,UAAI,YAAY,SAAS;AACvB,oBAAY,QAAQ,WAAW;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,iBAAiB,QAAQ,MAAM,QAAQ,YAAY,QAAQ,SAAS,CAAC;AAEzE,SAAO;AACT;","names":["import_react","import_react","import_react","import_react","query","import_react","import_react","entry","import_react"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// src/hooks/useDebounce.ts
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
function useDebounce(value, delay) {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const handler = setTimeout(() => {
|
|
7
|
+
setDebouncedValue(value);
|
|
8
|
+
}, delay);
|
|
9
|
+
return () => {
|
|
10
|
+
clearTimeout(handler);
|
|
11
|
+
};
|
|
12
|
+
}, [value, delay]);
|
|
13
|
+
return debouncedValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/hooks/useThrottle.ts
|
|
17
|
+
import { useState as useState2, useEffect as useEffect2, useRef } from "react";
|
|
18
|
+
function useThrottle(value, delay) {
|
|
19
|
+
const [throttledValue, setThrottledValue] = useState2(value);
|
|
20
|
+
const lastExecuted = useRef(Date.now());
|
|
21
|
+
useEffect2(() => {
|
|
22
|
+
const timeSinceLastExecution = Date.now() - lastExecuted.current;
|
|
23
|
+
if (timeSinceLastExecution >= delay) {
|
|
24
|
+
setThrottledValue(value);
|
|
25
|
+
lastExecuted.current = Date.now();
|
|
26
|
+
} else {
|
|
27
|
+
const timerId = setTimeout(() => {
|
|
28
|
+
setThrottledValue(value);
|
|
29
|
+
lastExecuted.current = Date.now();
|
|
30
|
+
}, delay - timeSinceLastExecution);
|
|
31
|
+
return () => clearTimeout(timerId);
|
|
32
|
+
}
|
|
33
|
+
}, [value, delay]);
|
|
34
|
+
return throttledValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/hooks/usePrevious.ts
|
|
38
|
+
import { useRef as useRef2, useEffect as useEffect3 } from "react";
|
|
39
|
+
function usePrevious(value) {
|
|
40
|
+
const ref = useRef2();
|
|
41
|
+
useEffect3(() => {
|
|
42
|
+
ref.current = value;
|
|
43
|
+
}, [value]);
|
|
44
|
+
return ref.current;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/hooks/useLocalStorage.ts
|
|
48
|
+
import { useState as useState3 } from "react";
|
|
49
|
+
function useLocalStorage(key, initialValue) {
|
|
50
|
+
const [storedValue, setStoredValue] = useState3(() => {
|
|
51
|
+
try {
|
|
52
|
+
if (typeof window === "undefined") {
|
|
53
|
+
return initialValue;
|
|
54
|
+
}
|
|
55
|
+
const item = window.localStorage.getItem(key);
|
|
56
|
+
return item ? JSON.parse(item) : initialValue;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.warn(`Error reading localStorage key "${key}":`, error);
|
|
59
|
+
return initialValue;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const setValue = (value) => {
|
|
63
|
+
try {
|
|
64
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
65
|
+
setStoredValue(valueToStore);
|
|
66
|
+
if (typeof window !== "undefined") {
|
|
67
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn(`Error setting localStorage key "${key}":`, error);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return [storedValue, setValue];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/hooks/useMediaQuery.ts
|
|
77
|
+
import { useState as useState4, useEffect as useEffect4 } from "react";
|
|
78
|
+
function useMediaQuery(query) {
|
|
79
|
+
const getMatches = (query2) => {
|
|
80
|
+
if (typeof window !== "undefined") {
|
|
81
|
+
return window.matchMedia(query2).matches;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
const [matches, setMatches] = useState4(() => getMatches(query));
|
|
86
|
+
useEffect4(() => {
|
|
87
|
+
function handleChange() {
|
|
88
|
+
setMatches(getMatches(query));
|
|
89
|
+
}
|
|
90
|
+
const matchMedia = window.matchMedia(query);
|
|
91
|
+
handleChange();
|
|
92
|
+
if (matchMedia.addListener) {
|
|
93
|
+
matchMedia.addListener(handleChange);
|
|
94
|
+
} else {
|
|
95
|
+
matchMedia.addEventListener("change", handleChange);
|
|
96
|
+
}
|
|
97
|
+
return () => {
|
|
98
|
+
if (matchMedia.removeListener) {
|
|
99
|
+
matchMedia.removeListener(handleChange);
|
|
100
|
+
} else {
|
|
101
|
+
matchMedia.removeEventListener("change", handleChange);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}, [query]);
|
|
105
|
+
return matches;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/hooks/useAsync.ts
|
|
109
|
+
import { useState as useState5, useCallback, useEffect as useEffect5 } from "react";
|
|
110
|
+
function useAsync(asyncFunction, immediate = true) {
|
|
111
|
+
const [state, setState] = useState5({
|
|
112
|
+
status: "idle",
|
|
113
|
+
value: null,
|
|
114
|
+
error: null
|
|
115
|
+
});
|
|
116
|
+
const execute = useCallback(() => {
|
|
117
|
+
setState({ status: "pending", value: null, error: null });
|
|
118
|
+
return asyncFunction().then((response) => {
|
|
119
|
+
setState({ status: "success", value: response, error: null });
|
|
120
|
+
return response;
|
|
121
|
+
}).catch((error) => {
|
|
122
|
+
setState({ status: "error", value: null, error });
|
|
123
|
+
});
|
|
124
|
+
}, [asyncFunction]);
|
|
125
|
+
useEffect5(() => {
|
|
126
|
+
if (immediate) {
|
|
127
|
+
execute();
|
|
128
|
+
}
|
|
129
|
+
}, [execute, immediate]);
|
|
130
|
+
return { ...state, execute };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/hooks/useIntersectionObserver.ts
|
|
134
|
+
import { useEffect as useEffect6, useState as useState6 } from "react";
|
|
135
|
+
function useIntersectionObserver(elementRef, { threshold = 0, root = null, rootMargin = "0%", freezeOnceVisible = false }) {
|
|
136
|
+
const [entry, setEntry] = useState6();
|
|
137
|
+
const frozen = entry?.isIntersecting && freezeOnceVisible;
|
|
138
|
+
const updateEntry = ([entry2]) => {
|
|
139
|
+
setEntry(entry2);
|
|
140
|
+
};
|
|
141
|
+
useEffect6(() => {
|
|
142
|
+
const node = elementRef?.current;
|
|
143
|
+
const hasIOSupport = !!window.IntersectionObserver;
|
|
144
|
+
if (!hasIOSupport || frozen || !node)
|
|
145
|
+
return;
|
|
146
|
+
const observerParams = { threshold, root, rootMargin };
|
|
147
|
+
const observer = new IntersectionObserver(updateEntry, observerParams);
|
|
148
|
+
observer.observe(node);
|
|
149
|
+
return () => observer.disconnect();
|
|
150
|
+
}, [elementRef, threshold, root, rootMargin, frozen]);
|
|
151
|
+
return entry;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/hooks/useInfiniteScroll.ts
|
|
155
|
+
import { useEffect as useEffect7, useRef as useRef3, useCallback as useCallback2 } from "react";
|
|
156
|
+
function useInfiniteScroll(callback, options = { rootMargin: "100px" }) {
|
|
157
|
+
const observerRef = useRef3(null);
|
|
158
|
+
const targetRef = useRef3(null);
|
|
159
|
+
const handleIntersect = useCallback2(
|
|
160
|
+
(entries) => {
|
|
161
|
+
const [entry] = entries;
|
|
162
|
+
if (entry.isIntersecting) {
|
|
163
|
+
callback();
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
[callback]
|
|
167
|
+
);
|
|
168
|
+
useEffect7(() => {
|
|
169
|
+
if (!targetRef.current)
|
|
170
|
+
return;
|
|
171
|
+
observerRef.current = new IntersectionObserver(handleIntersect, options);
|
|
172
|
+
observerRef.current.observe(targetRef.current);
|
|
173
|
+
return () => {
|
|
174
|
+
if (observerRef.current) {
|
|
175
|
+
observerRef.current.disconnect();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}, [handleIntersect, options.root, options.rootMargin, options.threshold]);
|
|
179
|
+
return targetRef;
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
useAsync,
|
|
183
|
+
useDebounce,
|
|
184
|
+
useInfiniteScroll,
|
|
185
|
+
useIntersectionObserver,
|
|
186
|
+
useLocalStorage,
|
|
187
|
+
useMediaQuery,
|
|
188
|
+
usePrevious,
|
|
189
|
+
useThrottle
|
|
190
|
+
};
|
|
191
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useDebounce.ts","../src/hooks/useThrottle.ts","../src/hooks/usePrevious.ts","../src/hooks/useLocalStorage.ts","../src/hooks/useMediaQuery.ts","../src/hooks/useAsync.ts","../src/hooks/useIntersectionObserver.ts","../src/hooks/useInfiniteScroll.ts"],"sourcesContent":["import { useState, useEffect } from 'react';\n\n/**\n * Delays updating the value until after `delay` milliseconds have elapsed.\n *\n * @param value The value to debounce.\n * @param delay The delay in milliseconds.\n * @returns The debounced value.\n */\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(handler);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n","import { useState, useEffect, useRef } from 'react';\n\n/**\n * Returns a throttled version of the value that only updates at most once every `delay` milliseconds.\n *\n * @param value The value to throttle.\n * @param delay The throttle limit in milliseconds.\n * @returns The throttled value.\n */\nexport function useThrottle<T>(value: T, delay: number): T {\n const [throttledValue, setThrottledValue] = useState<T>(value);\n const lastExecuted = useRef<number>(Date.now());\n\n useEffect(() => {\n const timeSinceLastExecution = Date.now() - lastExecuted.current;\n\n if (timeSinceLastExecution >= delay) {\n setThrottledValue(value);\n lastExecuted.current = Date.now();\n } else {\n const timerId = setTimeout(() => {\n setThrottledValue(value);\n lastExecuted.current = Date.now();\n }, delay - timeSinceLastExecution);\n\n return () => clearTimeout(timerId);\n }\n }, [value, delay]);\n\n return throttledValue;\n}\n","import { useRef, useEffect } from 'react';\n\n/**\n * Stores the previous state or prop value.\n *\n * @param value The value to track.\n * @returns The previous value.\n */\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>();\n\n useEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref.current;\n}\n","import { useState } from 'react';\n\n/**\n * Syncs state to local storage so that it persists through a page refresh.\n *\n * @param key The local storage key.\n * @param initialValue The initial value.\n * @returns A tuple containing the state value and the setter function.\n */\nexport function useLocalStorage<T>(key: string, initialValue: T) {\n const [storedValue, setStoredValue] = useState<T>(() => {\n try {\n if (typeof window === 'undefined') {\n return initialValue;\n }\n const item = window.localStorage.getItem(key);\n return item ? JSON.parse(item) : initialValue;\n } catch (error) {\n console.warn(`Error reading localStorage key \"${key}\":`, error);\n return initialValue;\n }\n });\n\n const setValue = (value: T | ((val: T) => T)) => {\n try {\n const valueToStore = value instanceof Function ? value(storedValue) : value;\n setStoredValue(valueToStore);\n if (typeof window !== 'undefined') {\n window.localStorage.setItem(key, JSON.stringify(valueToStore));\n }\n } catch (error) {\n console.warn(`Error setting localStorage key \"${key}\":`, error);\n }\n };\n\n return [storedValue, setValue] as const;\n}\n","import { useState, useEffect } from 'react';\n\n/**\n * Tracks the state of a CSS media query.\n *\n * @param query The media query to track.\n * @returns True if the media query matches, false otherwise.\n */\nexport function useMediaQuery(query: string): boolean {\n const getMatches = (query: string): boolean => {\n if (typeof window !== 'undefined') {\n return window.matchMedia(query).matches;\n }\n return false;\n };\n\n const [matches, setMatches] = useState<boolean>(() => getMatches(query));\n\n useEffect(() => {\n function handleChange() {\n setMatches(getMatches(query));\n }\n\n const matchMedia = window.matchMedia(query);\n handleChange();\n\n if (matchMedia.addListener) {\n matchMedia.addListener(handleChange);\n } else {\n matchMedia.addEventListener('change', handleChange);\n }\n\n return () => {\n if (matchMedia.removeListener) {\n matchMedia.removeListener(handleChange);\n } else {\n matchMedia.removeEventListener('change', handleChange);\n }\n };\n }, [query]);\n\n return matches;\n}\n","import { useState, useCallback, useEffect } from 'react';\n\ntype AsyncState<T> = {\n status: 'idle' | 'pending' | 'success' | 'error';\n value: T | null;\n error: Error | null;\n};\n\n/**\n * Handles asynchronous operations (promises) providing state.\n *\n * @param asyncFunction The async function to execute.\n * @param immediate Whether to execute the function immediately.\n * @returns The state and execute function.\n */\nexport function useAsync<T>(\n asyncFunction: () => Promise<T>,\n immediate = true\n) {\n const [state, setState] = useState<AsyncState<T>>({\n status: 'idle',\n value: null,\n error: null,\n });\n\n const execute = useCallback(() => {\n setState({ status: 'pending', value: null, error: null });\n\n return asyncFunction()\n .then((response) => {\n setState({ status: 'success', value: response, error: null });\n return response;\n })\n .catch((error) => {\n setState({ status: 'error', value: null, error });\n });\n }, [asyncFunction]);\n\n useEffect(() => {\n if (immediate) {\n execute();\n }\n }, [execute, immediate]);\n\n return { ...state, execute };\n}\n","import { useEffect, useState, RefObject } from 'react';\n\ninterface Args extends IntersectionObserverInit {\n freezeOnceVisible?: boolean;\n}\n\n/**\n * Tracks the intersection of a target element with an ancestor element or with a top-level document's viewport.\n *\n * @param elementRef The ref of the element to observe.\n * @param options Observer options and freezeOnceVisible.\n * @returns The IntersectionObserverEntry.\n */\nexport function useIntersectionObserver(\n elementRef: RefObject<Element>,\n { threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false }: Args\n): IntersectionObserverEntry | undefined {\n const [entry, setEntry] = useState<IntersectionObserverEntry>();\n\n const frozen = entry?.isIntersecting && freezeOnceVisible;\n\n const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {\n setEntry(entry);\n };\n\n useEffect(() => {\n const node = elementRef?.current;\n const hasIOSupport = !!window.IntersectionObserver;\n\n if (!hasIOSupport || frozen || !node) return;\n\n const observerParams = { threshold, root, rootMargin };\n const observer = new IntersectionObserver(updateEntry, observerParams);\n\n observer.observe(node);\n\n return () => observer.disconnect();\n }, [elementRef, threshold, root, rootMargin, frozen]);\n\n return entry;\n}\n","import { useEffect, useRef, useCallback } from 'react';\n\n/**\n * Uses useIntersectionObserver to trigger a callback when the user scrolls near the bottom of a container.\n *\n * @param callback The callback to execute when intersecting.\n * @param options Intersection options.\n * @returns A ref to attach to the target element.\n */\nexport function useInfiniteScroll<T extends HTMLElement>(\n callback: () => void,\n options: IntersectionObserverInit = { rootMargin: '100px' }\n) {\n const observerRef = useRef<IntersectionObserver | null>(null);\n const targetRef = useRef<T | null>(null);\n\n const handleIntersect = useCallback(\n (entries: IntersectionObserverEntry[]) => {\n const [entry] = entries;\n if (entry.isIntersecting) {\n callback();\n }\n },\n [callback]\n );\n\n useEffect(() => {\n if (!targetRef.current) return;\n\n observerRef.current = new IntersectionObserver(handleIntersect, options);\n observerRef.current.observe(targetRef.current);\n\n return () => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n };\n }, [handleIntersect, options.root, options.rootMargin, options.threshold]);\n\n return targetRef;\n}\n"],"mappings":"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","names":["useState","useEffect","useRef","useEffect","useState","useState","useEffect","query","useState","useEffect","useEffect","useState","entry","useEffect","useRef","useCallback"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@malconlobo/react-utility-hooks",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A collection of utility React hooks.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"test": "vitest",
|
|
22
|
+
"coverage": "vitest run --coverage",
|
|
23
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react",
|
|
28
|
+
"hooks",
|
|
29
|
+
"utility",
|
|
30
|
+
"useDebounce",
|
|
31
|
+
"useThrottle",
|
|
32
|
+
"usePrevious",
|
|
33
|
+
"useLocalStorage",
|
|
34
|
+
"useMediaQuery",
|
|
35
|
+
"useAsync",
|
|
36
|
+
"useInfiniteScroll",
|
|
37
|
+
"useIntersectionObserver"
|
|
38
|
+
],
|
|
39
|
+
"author": "",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/malconlobo/react-utility-hooks.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/malconlobo/react-utility-hooks/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/malconlobo/react-utility-hooks#readme",
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
51
|
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@testing-library/react": "^14.0.0",
|
|
55
|
+
"@types/react": "^18.2.0",
|
|
56
|
+
"@types/react-dom": "^18.2.0",
|
|
57
|
+
"eslint": "^8.0.0",
|
|
58
|
+
"eslint-config-prettier": "^9.0.0",
|
|
59
|
+
"eslint-plugin-react": "^7.33.0",
|
|
60
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
61
|
+
"jsdom": "^22.0.0",
|
|
62
|
+
"prettier": "^3.0.0",
|
|
63
|
+
"tsup": "^7.2.0",
|
|
64
|
+
"typescript": "^5.2.0",
|
|
65
|
+
"vitest": "^0.34.0"
|
|
66
|
+
}
|
|
67
|
+
}
|