use-good-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 +343 -0
- package/dist-export/index.d.ts +1 -0
- package/dist-export/index.js +192 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# 🪝 use-good-hooks
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
[](https://vitest.dev/)
|
|
8
|
+
|
|
9
|
+
A collection of well-tested, performance-optimized React hooks for common web application patterns. These hooks help manage state, UI interactions, and browser features with clean, reusable abstractions.
|
|
10
|
+
|
|
11
|
+
## 📦 Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Using npm
|
|
15
|
+
npm install use-good-hooks
|
|
16
|
+
|
|
17
|
+
# Using yarn
|
|
18
|
+
yarn add use-good-hooks
|
|
19
|
+
|
|
20
|
+
# Using pnpm
|
|
21
|
+
pnpm add use-good-hooks
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 🔌 Requirements
|
|
25
|
+
|
|
26
|
+
- React 19.0.0+
|
|
27
|
+
- Lodash 4.17.21+
|
|
28
|
+
|
|
29
|
+
## 🧰 Available Hooks
|
|
30
|
+
|
|
31
|
+
### `useDebounce`
|
|
32
|
+
|
|
33
|
+
Debounces value changes to prevent rapid updates. Useful for search inputs, form validation, and other scenarios where you want to delay state updates until after a user has stopped changing the input.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { useDebounce } from 'use-good-hooks';
|
|
37
|
+
|
|
38
|
+
function SearchComponent() {
|
|
39
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
40
|
+
const debouncedSearchTerm = useDebounce(searchTerm, 500); // 500ms delay
|
|
41
|
+
|
|
42
|
+
// API call will only happen 500ms after the user stops typing
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (debouncedSearchTerm) {
|
|
45
|
+
searchAPI(debouncedSearchTerm);
|
|
46
|
+
}
|
|
47
|
+
}, [debouncedSearchTerm]);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<input
|
|
51
|
+
type="text"
|
|
52
|
+
value={searchTerm}
|
|
53
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
54
|
+
placeholder="Search..."
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Parameters
|
|
61
|
+
|
|
62
|
+
- `value`: The value to debounce
|
|
63
|
+
- `delay`: (Optional) The delay in milliseconds (default: 300ms)
|
|
64
|
+
|
|
65
|
+
#### Returns
|
|
66
|
+
|
|
67
|
+
- The debounced value
|
|
68
|
+
|
|
69
|
+
### `useThrottle`
|
|
70
|
+
|
|
71
|
+
Limits the rate at which a value can update. Useful for scroll events, window resizing, and other high-frequency events.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { useThrottle } from 'use-good-hooks';
|
|
75
|
+
|
|
76
|
+
function ScrollTracker() {
|
|
77
|
+
const [scrollY, setScrollY] = useState(0);
|
|
78
|
+
const throttledScrollY = useThrottle(scrollY, 200); // 200ms throttle
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const handleScroll = () => {
|
|
82
|
+
setScrollY(window.scrollY);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
window.addEventListener('scroll', handleScroll);
|
|
86
|
+
return () => window.removeEventListener('scroll', handleScroll);
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
return <div>Throttled scroll position: {throttledScrollY}px</div>;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Parameters
|
|
94
|
+
|
|
95
|
+
- `value`: The value to throttle
|
|
96
|
+
- `delay`: (Optional) The throttle interval in milliseconds (default: 300ms)
|
|
97
|
+
|
|
98
|
+
#### Returns
|
|
99
|
+
|
|
100
|
+
- The throttled value
|
|
101
|
+
|
|
102
|
+
### `usePrev`
|
|
103
|
+
|
|
104
|
+
Captures the previous value of a state or prop. Useful for comparing changes between renders.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { usePrev } from 'use-good-hooks';
|
|
108
|
+
|
|
109
|
+
function Counter({ count }) {
|
|
110
|
+
const prevCount = usePrev(count);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div>
|
|
114
|
+
<p>Current count: {count}</p>
|
|
115
|
+
<p>Previous count: {prevCount ?? 'None'}</p>
|
|
116
|
+
<p>Direction: {count > prevCount ? 'Increasing' : count < prevCount ? 'Decreasing' : 'No change'}</p>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Parameters
|
|
123
|
+
|
|
124
|
+
- `value`: The value to track
|
|
125
|
+
|
|
126
|
+
#### Returns
|
|
127
|
+
|
|
128
|
+
- The previous value (undefined on first render)
|
|
129
|
+
|
|
130
|
+
### `useDistinct`
|
|
131
|
+
|
|
132
|
+
Detects distinct changes in values with support for deep comparison and custom equality checks. Useful for tracking whether complex objects have actually changed.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { useDistinct } from 'use-good-hooks';
|
|
136
|
+
|
|
137
|
+
function UserProfileForm({ user }) {
|
|
138
|
+
const { distinct, value, prevValue } = useDistinct(user, { deep: true });
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (distinct) {
|
|
142
|
+
console.log('User data changed from:', prevValue, 'to:', value);
|
|
143
|
+
// Perhaps save to backend or update UI
|
|
144
|
+
}
|
|
145
|
+
}, [distinct, prevValue, value]);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div>
|
|
149
|
+
<h2>Editing profile for: {user.name}</h2>
|
|
150
|
+
{distinct && <div className="alert">Unsaved changes!</div>}
|
|
151
|
+
{/* Form inputs */}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Parameters
|
|
158
|
+
|
|
159
|
+
- `inputValue`: The value to check for changes
|
|
160
|
+
- `options`: (Optional) Configuration options:
|
|
161
|
+
- `deep`: Boolean to enable deep equality comparison (default: false)
|
|
162
|
+
- `compare`: Custom comparison function (a, b) => boolean
|
|
163
|
+
- `debounce`: Debounce time in milliseconds (default: 0)
|
|
164
|
+
|
|
165
|
+
#### Returns
|
|
166
|
+
|
|
167
|
+
- Object with:
|
|
168
|
+
- `distinct`: Boolean indicating if the value changed
|
|
169
|
+
- `prevValue`: The previous distinct value
|
|
170
|
+
- `value`: The current value
|
|
171
|
+
|
|
172
|
+
### `useStorageState`
|
|
173
|
+
|
|
174
|
+
Persists state to localStorage or sessionStorage with automatic serialization/deserialization.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { useStorageState } from 'use-good-hooks';
|
|
178
|
+
|
|
179
|
+
function ThemePreferences() {
|
|
180
|
+
const [preferences, setPreferences, { removeKey }] = useStorageState('theme-prefs', {
|
|
181
|
+
darkMode: false,
|
|
182
|
+
fontSize: 'medium',
|
|
183
|
+
compactView: true
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div>
|
|
188
|
+
<h2>Theme Settings</h2>
|
|
189
|
+
<label>
|
|
190
|
+
<input
|
|
191
|
+
type="checkbox"
|
|
192
|
+
checked={preferences.darkMode}
|
|
193
|
+
onChange={() => setPreferences({...preferences, darkMode: !preferences.darkMode})}
|
|
194
|
+
/>
|
|
195
|
+
Dark Mode
|
|
196
|
+
</label>
|
|
197
|
+
{/* More settings */}
|
|
198
|
+
<button onClick={removeKey}>Reset to Defaults</button>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### Parameters
|
|
205
|
+
|
|
206
|
+
- `key`: Storage key name
|
|
207
|
+
- `initialState`: Default state if no stored value exists
|
|
208
|
+
- `options`: (Optional) Configuration options:
|
|
209
|
+
- `storage`: 'local' or 'session' (default: 'local')
|
|
210
|
+
- `debounce`: Debounce time in milliseconds (default: 500)
|
|
211
|
+
- `onError`: Error callback function
|
|
212
|
+
- `omitKeys`: Array of keys to omit from storage or function (value, key) => boolean
|
|
213
|
+
- `pickKeys`: Array of keys to include in storage or function (value, key) => boolean
|
|
214
|
+
|
|
215
|
+
#### Returns
|
|
216
|
+
|
|
217
|
+
- Array with:
|
|
218
|
+
- State value
|
|
219
|
+
- State setter function
|
|
220
|
+
- Object with utility functions:
|
|
221
|
+
- `removeKey`: Function to clear the storage key
|
|
222
|
+
|
|
223
|
+
### `useUrlState`
|
|
224
|
+
|
|
225
|
+
Synchronizes state with URL query parameters. Great for shareable UI states, filters, pagination, and search terms.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { useUrlState } from 'use-good-hooks';
|
|
229
|
+
|
|
230
|
+
function ProductFilter() {
|
|
231
|
+
const [filters, setFilters] = useUrlState({
|
|
232
|
+
category: '',
|
|
233
|
+
minPrice: 0,
|
|
234
|
+
maxPrice: 1000,
|
|
235
|
+
sortBy: 'newest'
|
|
236
|
+
}, {
|
|
237
|
+
url: new URL(window.location.href),
|
|
238
|
+
kebabCase: true,
|
|
239
|
+
omitValues: ['', 0]
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<div>
|
|
244
|
+
<select
|
|
245
|
+
value={filters.category}
|
|
246
|
+
onChange={(e) => setFilters({...filters, category: e.target.value})}
|
|
247
|
+
>
|
|
248
|
+
<option value="">All Categories</option>
|
|
249
|
+
<option value="electronics">Electronics</option>
|
|
250
|
+
<option value="clothing">Clothing</option>
|
|
251
|
+
</select>
|
|
252
|
+
{/* More filter controls */}
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### Parameters
|
|
259
|
+
|
|
260
|
+
- `initialState`: Default state if URL has no parameters
|
|
261
|
+
- `options`: Configuration options:
|
|
262
|
+
- `url`: URL object to use (required)
|
|
263
|
+
- `debounce`: Debounce time in milliseconds (default: 500)
|
|
264
|
+
- `kebabCase`: Convert camelCase keys to kebab-case in URL (default: true)
|
|
265
|
+
- `prefix`: Optional prefix for URL parameters
|
|
266
|
+
- `onError`: Error callback function
|
|
267
|
+
- `omitKeys`: Array of keys to omit from URL or function (value, key) => boolean
|
|
268
|
+
- `pickKeys`: Array of keys to include in URL or function (value, key) => boolean
|
|
269
|
+
- `omitValues`: Array of values to omit from URL or function (value, key) => boolean
|
|
270
|
+
|
|
271
|
+
#### Returns
|
|
272
|
+
|
|
273
|
+
- Array with:
|
|
274
|
+
- State value
|
|
275
|
+
- State setter function
|
|
276
|
+
|
|
277
|
+
## 🧪 Running Tests
|
|
278
|
+
|
|
279
|
+
This library is thoroughly tested with Vitest and React Testing Library. To run the tests:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# Using npm
|
|
283
|
+
npm test
|
|
284
|
+
|
|
285
|
+
# Using yarn
|
|
286
|
+
yarn test
|
|
287
|
+
|
|
288
|
+
# Using pnpm
|
|
289
|
+
pnpm test
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## 🔄 How hooks update and optimize performance
|
|
293
|
+
|
|
294
|
+
Each hook in this library is designed with performance in mind:
|
|
295
|
+
|
|
296
|
+
1. `useDebounce` and `useThrottle` reduce unnecessary renders using Lodash's optimized implementations
|
|
297
|
+
2. `useDistinct` avoids reference equality problems with optional deep comparison
|
|
298
|
+
3. `useStorageState` batches storage updates to reduce expensive serialization/deserialization
|
|
299
|
+
4. `useUrlState` efficiently handles URL synchronization with debouncing
|
|
300
|
+
|
|
301
|
+
## 🛠️ Development
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
# Install dependencies
|
|
305
|
+
yarn install
|
|
306
|
+
|
|
307
|
+
# Start development server
|
|
308
|
+
yarn dev
|
|
309
|
+
|
|
310
|
+
# Run tests
|
|
311
|
+
yarn test
|
|
312
|
+
|
|
313
|
+
# Build the library
|
|
314
|
+
yarn build
|
|
315
|
+
|
|
316
|
+
# Lint and format the code
|
|
317
|
+
yarn lint
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## 🙏 Acknowledgements
|
|
321
|
+
|
|
322
|
+
This project was built with:
|
|
323
|
+
|
|
324
|
+
- [React](https://reactjs.org/)
|
|
325
|
+
- [TypeScript](https://www.typescriptlang.org/)
|
|
326
|
+
- [Vite](https://vitejs.dev/)
|
|
327
|
+
- [Vitest](https://vitest.dev/)
|
|
328
|
+
- [Lodash](https://lodash.com/)
|
|
329
|
+
- [use-json](https://www.npmjs.com/package/use-json)
|
|
330
|
+
- [use-qs](https://www.npmjs.com/package/use-qs)
|
|
331
|
+
|
|
332
|
+
## 📝 License
|
|
333
|
+
|
|
334
|
+
MIT © [Felipe Rohde](mailto:feliperohdee@gmail.com)
|
|
335
|
+
|
|
336
|
+
## 👨💻 Author
|
|
337
|
+
|
|
338
|
+
**Felipe Rohde**
|
|
339
|
+
|
|
340
|
+
- Twitter: [@felipe_rohde](https://twitter.com/felipe_rohde)
|
|
341
|
+
- Github: [@feliperohdee](https://github.com/feliperohdee)
|
|
342
|
+
- Email: feliperohdee@gmail.com
|
|
343
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { }
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import t from "lodash";
|
|
2
|
+
import { useState as S, useRef as b, useEffect as y, useCallback as D } from "react";
|
|
3
|
+
import k from "use-json";
|
|
4
|
+
import w from "use-qs";
|
|
5
|
+
const _ = 300, R = (r, e = _) => {
|
|
6
|
+
const [l, d] = S(r), f = b(
|
|
7
|
+
t.debounce((p) => {
|
|
8
|
+
d(p);
|
|
9
|
+
}, e)
|
|
10
|
+
);
|
|
11
|
+
return y(() => {
|
|
12
|
+
f.current(r);
|
|
13
|
+
}, [r]), y(() => {
|
|
14
|
+
const p = f.current;
|
|
15
|
+
return () => {
|
|
16
|
+
p.cancel();
|
|
17
|
+
};
|
|
18
|
+
}, []), l;
|
|
19
|
+
}, B = (r, e) => r === e, M = (r, e = {}) => {
|
|
20
|
+
const l = b(!1), d = b(e), f = b(r), p = b(
|
|
21
|
+
t.debounce((i) => {
|
|
22
|
+
const { compare: o, deep: s } = d.current;
|
|
23
|
+
!(t.isFunction(o) ? o : s ? t.isEqual : B)(i, f.current) && (F({
|
|
24
|
+
distinct: !0,
|
|
25
|
+
prevValue: f.current,
|
|
26
|
+
value: i
|
|
27
|
+
}), f.current = i);
|
|
28
|
+
}, d.current.debounce ?? 0)
|
|
29
|
+
), [m, F] = S({
|
|
30
|
+
distinct: !1,
|
|
31
|
+
prevValue: void 0,
|
|
32
|
+
value: r
|
|
33
|
+
}), a = D(() => {
|
|
34
|
+
l.current = !1, F((i) => ({
|
|
35
|
+
...i,
|
|
36
|
+
distinct: !1
|
|
37
|
+
}));
|
|
38
|
+
}, []);
|
|
39
|
+
return y(() => {
|
|
40
|
+
if (m.distinct && l.current) {
|
|
41
|
+
a();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const i = p.current;
|
|
45
|
+
i(r);
|
|
46
|
+
}), y(() => {
|
|
47
|
+
m.distinct && (l.current = !0);
|
|
48
|
+
}, [m.distinct]), y(() => {
|
|
49
|
+
const i = p.current;
|
|
50
|
+
return () => {
|
|
51
|
+
i.cancel();
|
|
52
|
+
};
|
|
53
|
+
}, []), m;
|
|
54
|
+
}, O = (r) => {
|
|
55
|
+
const e = b(void 0);
|
|
56
|
+
return y(() => {
|
|
57
|
+
e.current = r;
|
|
58
|
+
}), e.current;
|
|
59
|
+
}, A = () => typeof window < "u", g = { browser: A }, K = 500, h = "local", U = (r, e) => t.isMap(e) ? {
|
|
60
|
+
__type: "Map",
|
|
61
|
+
value: Array.from(e.entries())
|
|
62
|
+
} : t.isSet(e) ? {
|
|
63
|
+
__type: "Set",
|
|
64
|
+
value: Array.from(e)
|
|
65
|
+
} : e, z = (r, e) => e && e.__type === "Map" ? new Map(e.value) : e && e.__type === "Set" ? new Set(e.value) : e, $ = (r, e, l) => {
|
|
66
|
+
const d = b(l || {}), f = b(!1), [p, m] = S(e), F = R(
|
|
67
|
+
p,
|
|
68
|
+
d.current.debounce ?? K
|
|
69
|
+
);
|
|
70
|
+
y(() => {
|
|
71
|
+
if (!g.browser() || !f.current)
|
|
72
|
+
return;
|
|
73
|
+
const {
|
|
74
|
+
storage: i = h,
|
|
75
|
+
onError: o,
|
|
76
|
+
omitKeys: s,
|
|
77
|
+
pickKeys: u
|
|
78
|
+
} = d.current, c = i === "local" ? localStorage : sessionStorage;
|
|
79
|
+
if (c)
|
|
80
|
+
try {
|
|
81
|
+
let n = F;
|
|
82
|
+
s && (t.size(s) || t.isFunction(s)) && (n = t.isFunction(s) ? t.omitBy(n, s) : t.omit(n, s)), u && (t.size(u) || t.isFunction(u)) && (n = t.isFunction(u) ? t.pickBy(n, u) : t.pick(n, u)), c.setItem(r, k.stringify(n, U));
|
|
83
|
+
} catch (n) {
|
|
84
|
+
o && o(n);
|
|
85
|
+
}
|
|
86
|
+
}, [r, F]), y(() => {
|
|
87
|
+
const {
|
|
88
|
+
storage: i = h,
|
|
89
|
+
omitKeys: o,
|
|
90
|
+
onError: s,
|
|
91
|
+
pickKeys: u
|
|
92
|
+
} = d.current;
|
|
93
|
+
try {
|
|
94
|
+
const c = i === "local" ? localStorage : sessionStorage, n = c == null ? void 0 : c.getItem(r);
|
|
95
|
+
if (n) {
|
|
96
|
+
let E = k.parse(n, z);
|
|
97
|
+
o && (t.size(o) || t.isFunction(o)) && (E = t.isFunction(o) ? t.omitBy(E, o) : t.omit(E, o)), u && (t.size(u) || t.isFunction(u)) && (E = t.isFunction(u) ? t.pickBy(E, u) : t.pick(E, u)), m(
|
|
98
|
+
t.isEmpty(E) ? e : t.merge({}, e, E)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
} catch (c) {
|
|
102
|
+
s && s(c);
|
|
103
|
+
} finally {
|
|
104
|
+
f.current = !0;
|
|
105
|
+
}
|
|
106
|
+
}, []);
|
|
107
|
+
const a = D(() => {
|
|
108
|
+
if (!g.browser())
|
|
109
|
+
return;
|
|
110
|
+
m(e);
|
|
111
|
+
const { storage: i = h } = d.current, o = i === "local" ? localStorage : sessionStorage;
|
|
112
|
+
o == null || o.removeItem(r);
|
|
113
|
+
}, [r, e]);
|
|
114
|
+
return [p, m, { removeKey: a }];
|
|
115
|
+
}, C = 300, v = (r, e = C) => {
|
|
116
|
+
const [l, d] = S(r), f = b(
|
|
117
|
+
t.throttle((p) => {
|
|
118
|
+
d(p);
|
|
119
|
+
}, e)
|
|
120
|
+
);
|
|
121
|
+
return y(() => {
|
|
122
|
+
f.current(r);
|
|
123
|
+
}, [r]), y(() => {
|
|
124
|
+
const p = f.current;
|
|
125
|
+
return () => {
|
|
126
|
+
p.cancel();
|
|
127
|
+
};
|
|
128
|
+
}, []), l;
|
|
129
|
+
}, T = 500, x = (r, e) => {
|
|
130
|
+
const l = b(e), d = () => {
|
|
131
|
+
const {
|
|
132
|
+
kebabCase: F = !0,
|
|
133
|
+
omitKeys: a,
|
|
134
|
+
omitValues: i,
|
|
135
|
+
onError: o,
|
|
136
|
+
pickKeys: s,
|
|
137
|
+
prefix: u = "",
|
|
138
|
+
url: c
|
|
139
|
+
} = l.current;
|
|
140
|
+
if (!c.search)
|
|
141
|
+
return r;
|
|
142
|
+
try {
|
|
143
|
+
let n = w.parse(decodeURIComponent(c.search), {
|
|
144
|
+
case: F ? "kebab-case" : "camelCase",
|
|
145
|
+
omitValues: i,
|
|
146
|
+
prefix: u
|
|
147
|
+
});
|
|
148
|
+
return a && (t.size(a) || t.isFunction(a)) && (n = t.isFunction(a) ? t.omitBy(n, a) : t.omit(n, a)), s && (t.size(s) || t.isFunction(s)) && (n = t.isFunction(s) ? t.pickBy(n, s) : t.pick(n, s)), t.isEmpty(n) ? r : t.merge({}, r, n);
|
|
149
|
+
} catch (n) {
|
|
150
|
+
return o && o(n), r;
|
|
151
|
+
}
|
|
152
|
+
}, [f, p] = S(d), m = R(
|
|
153
|
+
f,
|
|
154
|
+
l.current.debounce ?? T
|
|
155
|
+
);
|
|
156
|
+
return y(() => {
|
|
157
|
+
if (!g.browser())
|
|
158
|
+
return;
|
|
159
|
+
const {
|
|
160
|
+
kebabCase: F = !0,
|
|
161
|
+
omitKeys: a,
|
|
162
|
+
omitValues: i,
|
|
163
|
+
onError: o,
|
|
164
|
+
pickKeys: s,
|
|
165
|
+
prefix: u = ""
|
|
166
|
+
} = l.current;
|
|
167
|
+
try {
|
|
168
|
+
let c = m;
|
|
169
|
+
a && (t.size(a) || t.isFunction(a)) && (c = t.isFunction(a) ? t.omitBy(c, a) : t.omit(c, a)), s && (t.size(s) || t.isFunction(s)) && (c = t.isFunction(s) ? t.pickBy(c, s) : t.pick(c, s));
|
|
170
|
+
const n = w.stringify(c, {
|
|
171
|
+
case: F ? "kebab-case" : "camelCase",
|
|
172
|
+
omitValues: i,
|
|
173
|
+
prefix: u
|
|
174
|
+
});
|
|
175
|
+
n ? window.history.replaceState(
|
|
176
|
+
{},
|
|
177
|
+
"",
|
|
178
|
+
`${window.location.pathname}${n}`
|
|
179
|
+
) : window.history.replaceState({}, "", window.location.pathname);
|
|
180
|
+
} catch (c) {
|
|
181
|
+
o && o(c);
|
|
182
|
+
}
|
|
183
|
+
}, [m]), [f, p];
|
|
184
|
+
};
|
|
185
|
+
export {
|
|
186
|
+
R as useDebounce,
|
|
187
|
+
M as useDistinct,
|
|
188
|
+
O as usePrev,
|
|
189
|
+
$ as useStorageState,
|
|
190
|
+
v as useThrottle,
|
|
191
|
+
x as useUrlState
|
|
192
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"use-json": "^1.0.0",
|
|
4
|
+
"use-qs": "^1.0.4"
|
|
5
|
+
},
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@eslint/js": "^9.19.0",
|
|
8
|
+
"@testing-library/dom": "^10.4.0",
|
|
9
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
10
|
+
"@testing-library/react": "^16.2.0",
|
|
11
|
+
"@types/lodash": "^4.17.16",
|
|
12
|
+
"@types/node": "^22.13.1",
|
|
13
|
+
"@types/react": "^19.0.8",
|
|
14
|
+
"@types/react-dom": "^19.0.3",
|
|
15
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
16
|
+
"clsx": "^2.1.1",
|
|
17
|
+
"eslint": "^9.19.0",
|
|
18
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
19
|
+
"eslint-plugin-react-refresh": "^0.4.18",
|
|
20
|
+
"globals": "^16.0.0",
|
|
21
|
+
"jsdom": "^26.0.0",
|
|
22
|
+
"lodash": "^4.17.21",
|
|
23
|
+
"prettier": "^3.4.2",
|
|
24
|
+
"react": "^19.0.0",
|
|
25
|
+
"react-dom": "^19.0.0",
|
|
26
|
+
"typescript": "~5.8.2",
|
|
27
|
+
"typescript-eslint": "^8.22.0",
|
|
28
|
+
"vite": "^6.2.0",
|
|
29
|
+
"vite-plugin-dts": "^4.5.3",
|
|
30
|
+
"vitest": "^3.0.7"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist-export"
|
|
34
|
+
],
|
|
35
|
+
"main": "dist-export/index.js",
|
|
36
|
+
"module": "index.ts",
|
|
37
|
+
"name": "use-good-hooks",
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"lodash": "^4.17.21",
|
|
40
|
+
"react": "^19.0.0",
|
|
41
|
+
"react-dom": "^19.0.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "vite build",
|
|
45
|
+
"build:export": "yarn lint && yarn build --outDir ./dist-export --mode export",
|
|
46
|
+
"dev": "vite",
|
|
47
|
+
"lint": "prettier --write . && eslint .",
|
|
48
|
+
"test": "vitest"
|
|
49
|
+
},
|
|
50
|
+
"type": "module",
|
|
51
|
+
"version": "1.0.0"
|
|
52
|
+
}
|