@allem-sdk/hooks 0.1.1 → 0.1.3

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 CHANGED
@@ -3,6 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
+ <a href="https://www.npmjs.com/package/@allem-sdk/hooks"><img src="https://img.shields.io/npm/v/@allem-sdk/hooks.svg" alt="npm version" /></a>
6
7
  <a href="https://github.com/kingofmit/allem-sdk/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License" /></a>
7
8
  <img src="https://img.shields.io/badge/react-19-61dafb" alt="React 19" />
8
9
  <img src="https://img.shields.io/badge/typescript-strict-blue" alt="TypeScript" />
@@ -24,6 +25,7 @@ npm install @allem-sdk/hooks
24
25
  | Hook | Description |
25
26
  |------|-------------|
26
27
  | `useDebounce` | Debounce a value by a given delay |
28
+ | `useThrottle` | Throttle a value, updating at most once per delay |
27
29
  | `useLocalStorage` | Persist state in localStorage with SSR safety |
28
30
  | `useMediaQuery` | Reactive CSS media query matching |
29
31
  | `useClickOutside` | Detect clicks outside a ref element |
@@ -31,6 +33,10 @@ npm install @allem-sdk/hooks
31
33
  | `useCopyToClipboard` | Copy text to clipboard with status |
32
34
  | `useIntersectionObserver` | Observe element visibility via IntersectionObserver |
33
35
  | `useWindowSize` | Reactive window dimensions |
36
+ | `useFetch` | Data fetching with loading/error states and refetch |
37
+ | `usePrevious` | Track the previous value of a variable |
38
+ | `useKeyPress` | Detect if a specific key is pressed |
39
+ | `useOnlineStatus` | Reactive browser online/offline status |
34
40
 
35
41
  ## Usage
36
42
 
@@ -62,6 +68,37 @@ function Dropdown() {
62
68
  }
63
69
  ```
64
70
 
71
+ ## More Examples
72
+
73
+ ```tsx
74
+ import { useFetch, useThrottle, useKeyPress, useOnlineStatus, usePrevious } from "@allem-sdk/hooks";
75
+
76
+ function UserList() {
77
+ const { data, error, isLoading, refetch } = useFetch<User[]>("/api/users");
78
+ const isOnline = useOnlineStatus();
79
+ const isEscPressed = useKeyPress("Escape");
80
+
81
+ if (!isOnline) return <p>You are offline</p>;
82
+ if (isLoading) return <p>Loading...</p>;
83
+ return <ul>{data?.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
84
+ }
85
+
86
+ function ScrollTracker() {
87
+ const [scroll, setScroll] = useState(0);
88
+ const throttledScroll = useThrottle(scroll, 100);
89
+ const prevScroll = usePrevious(throttledScroll);
90
+ const direction = prevScroll !== undefined && throttledScroll > prevScroll ? "down" : "up";
91
+
92
+ useEffect(() => {
93
+ const handler = () => setScroll(window.scrollY);
94
+ window.addEventListener("scroll", handler);
95
+ return () => window.removeEventListener("scroll", handler);
96
+ }, []);
97
+
98
+ return <p>Scrolling {direction}</p>;
99
+ }
100
+ ```
101
+
65
102
  ## Part of [Allem SDK](https://github.com/kingofmit/allem-sdk)
66
103
 
67
104
  This package can be used standalone or as part of the full SDK. Install `allem-sdk` to get all packages in one install.
package/dist/index.d.mts CHANGED
@@ -28,4 +28,66 @@ interface WindowSize {
28
28
  }
29
29
  declare function useWindowSize(): WindowSize;
30
30
 
31
- export { useClickOutside, useCopyToClipboard, useDebounce, useIntersectionObserver, useLocalStorage, useMediaQuery, useToggle, useWindowSize };
31
+ interface UseFetchState<T> {
32
+ data: T | null;
33
+ error: Error | null;
34
+ isLoading: boolean;
35
+ }
36
+ interface UseFetchReturn<T> extends UseFetchState<T> {
37
+ refetch: () => void;
38
+ }
39
+ /**
40
+ * Simple data fetching hook with loading/error states and refetch.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const { data, error, isLoading, refetch } = useFetch<User[]>("/api/users");
45
+ * ```
46
+ */
47
+ declare function useFetch<T = unknown>(url: string | null, options?: RequestInit): UseFetchReturn<T>;
48
+
49
+ /**
50
+ * Throttles a value, updating at most once per `delay` ms.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * const throttledScroll = useThrottle(scrollPosition, 100);
55
+ * ```
56
+ */
57
+ declare function useThrottle<T>(value: T, delay: number): T;
58
+
59
+ /**
60
+ * Returns the previous value of a variable.
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * const prevCount = usePrevious(count);
65
+ * // On first render: undefined
66
+ * // After count changes: previous count value
67
+ * ```
68
+ */
69
+ declare function usePrevious<T>(value: T): T | undefined;
70
+
71
+ /**
72
+ * Detects whether a specific key is currently pressed.
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * const isEscPressed = useKeyPress("Escape");
77
+ * const isEnterPressed = useKeyPress("Enter");
78
+ * ```
79
+ */
80
+ declare function useKeyPress(targetKey: string): boolean;
81
+
82
+ /**
83
+ * Tracks the browser's online/offline status reactively.
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * const isOnline = useOnlineStatus();
88
+ * // true when connected, false when offline
89
+ * ```
90
+ */
91
+ declare function useOnlineStatus(): boolean;
92
+
93
+ export { useClickOutside, useCopyToClipboard, useDebounce, useFetch, useIntersectionObserver, useKeyPress, useLocalStorage, useMediaQuery, useOnlineStatus, usePrevious, useThrottle, useToggle, useWindowSize };
package/dist/index.d.ts CHANGED
@@ -28,4 +28,66 @@ interface WindowSize {
28
28
  }
29
29
  declare function useWindowSize(): WindowSize;
30
30
 
31
- export { useClickOutside, useCopyToClipboard, useDebounce, useIntersectionObserver, useLocalStorage, useMediaQuery, useToggle, useWindowSize };
31
+ interface UseFetchState<T> {
32
+ data: T | null;
33
+ error: Error | null;
34
+ isLoading: boolean;
35
+ }
36
+ interface UseFetchReturn<T> extends UseFetchState<T> {
37
+ refetch: () => void;
38
+ }
39
+ /**
40
+ * Simple data fetching hook with loading/error states and refetch.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const { data, error, isLoading, refetch } = useFetch<User[]>("/api/users");
45
+ * ```
46
+ */
47
+ declare function useFetch<T = unknown>(url: string | null, options?: RequestInit): UseFetchReturn<T>;
48
+
49
+ /**
50
+ * Throttles a value, updating at most once per `delay` ms.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * const throttledScroll = useThrottle(scrollPosition, 100);
55
+ * ```
56
+ */
57
+ declare function useThrottle<T>(value: T, delay: number): T;
58
+
59
+ /**
60
+ * Returns the previous value of a variable.
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * const prevCount = usePrevious(count);
65
+ * // On first render: undefined
66
+ * // After count changes: previous count value
67
+ * ```
68
+ */
69
+ declare function usePrevious<T>(value: T): T | undefined;
70
+
71
+ /**
72
+ * Detects whether a specific key is currently pressed.
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * const isEscPressed = useKeyPress("Escape");
77
+ * const isEnterPressed = useKeyPress("Enter");
78
+ * ```
79
+ */
80
+ declare function useKeyPress(targetKey: string): boolean;
81
+
82
+ /**
83
+ * Tracks the browser's online/offline status reactively.
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * const isOnline = useOnlineStatus();
88
+ * // true when connected, false when offline
89
+ * ```
90
+ */
91
+ declare function useOnlineStatus(): boolean;
92
+
93
+ export { useClickOutside, useCopyToClipboard, useDebounce, useFetch, useIntersectionObserver, useKeyPress, useLocalStorage, useMediaQuery, useOnlineStatus, usePrevious, useThrottle, useToggle, useWindowSize };
package/dist/index.js CHANGED
@@ -24,9 +24,14 @@ __export(index_exports, {
24
24
  useClickOutside: () => useClickOutside,
25
25
  useCopyToClipboard: () => useCopyToClipboard,
26
26
  useDebounce: () => useDebounce,
27
+ useFetch: () => useFetch,
27
28
  useIntersectionObserver: () => useIntersectionObserver,
29
+ useKeyPress: () => useKeyPress,
28
30
  useLocalStorage: () => useLocalStorage,
29
31
  useMediaQuery: () => useMediaQuery,
32
+ useOnlineStatus: () => useOnlineStatus,
33
+ usePrevious: () => usePrevious,
34
+ useThrottle: () => useThrottle,
30
35
  useToggle: () => useToggle,
31
36
  useWindowSize: () => useWindowSize
32
37
  });
@@ -162,14 +167,125 @@ function useWindowSize() {
162
167
  }, []);
163
168
  return size;
164
169
  }
170
+
171
+ // src/hooks/useFetch.ts
172
+ var import_react9 = require("react");
173
+ function useFetch(url, options) {
174
+ const [state, setState] = (0, import_react9.useState)({
175
+ data: null,
176
+ error: null,
177
+ isLoading: !!url
178
+ });
179
+ const optionsRef = (0, import_react9.useRef)(options);
180
+ optionsRef.current = options;
181
+ const fetchData = (0, import_react9.useCallback)(async () => {
182
+ if (!url) return;
183
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
184
+ try {
185
+ const res = await fetch(url, optionsRef.current);
186
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
187
+ const data = await res.json();
188
+ setState({ data, error: null, isLoading: false });
189
+ } catch (err) {
190
+ setState({ data: null, error: err, isLoading: false });
191
+ }
192
+ }, [url]);
193
+ (0, import_react9.useEffect)(() => {
194
+ fetchData();
195
+ }, [fetchData]);
196
+ return { ...state, refetch: fetchData };
197
+ }
198
+
199
+ // src/hooks/useThrottle.ts
200
+ var import_react10 = require("react");
201
+ function useThrottle(value, delay) {
202
+ const [throttled, setThrottled] = (0, import_react10.useState)(value);
203
+ const lastUpdated = (0, import_react10.useRef)(Date.now());
204
+ const timeoutRef = (0, import_react10.useRef)(void 0);
205
+ (0, import_react10.useEffect)(() => {
206
+ const now = Date.now();
207
+ const elapsed = now - lastUpdated.current;
208
+ if (elapsed >= delay) {
209
+ setThrottled(value);
210
+ lastUpdated.current = now;
211
+ } else {
212
+ clearTimeout(timeoutRef.current);
213
+ timeoutRef.current = setTimeout(() => {
214
+ setThrottled(value);
215
+ lastUpdated.current = Date.now();
216
+ }, delay - elapsed);
217
+ }
218
+ return () => clearTimeout(timeoutRef.current);
219
+ }, [value, delay]);
220
+ return throttled;
221
+ }
222
+
223
+ // src/hooks/usePrevious.ts
224
+ var import_react11 = require("react");
225
+ function usePrevious(value) {
226
+ const ref = (0, import_react11.useRef)(void 0);
227
+ (0, import_react11.useEffect)(() => {
228
+ ref.current = value;
229
+ }, [value]);
230
+ return ref.current;
231
+ }
232
+
233
+ // src/hooks/useKeyPress.ts
234
+ var import_react12 = require("react");
235
+ function useKeyPress(targetKey) {
236
+ const [isPressed, setIsPressed] = (0, import_react12.useState)(false);
237
+ (0, import_react12.useEffect)(() => {
238
+ if (typeof window === "undefined") return;
239
+ const handleDown = (e) => {
240
+ if (e.key === targetKey) setIsPressed(true);
241
+ };
242
+ const handleUp = (e) => {
243
+ if (e.key === targetKey) setIsPressed(false);
244
+ };
245
+ window.addEventListener("keydown", handleDown);
246
+ window.addEventListener("keyup", handleUp);
247
+ return () => {
248
+ window.removeEventListener("keydown", handleDown);
249
+ window.removeEventListener("keyup", handleUp);
250
+ };
251
+ }, [targetKey]);
252
+ return isPressed;
253
+ }
254
+
255
+ // src/hooks/useOnlineStatus.ts
256
+ var import_react13 = require("react");
257
+ function subscribe(callback) {
258
+ if (typeof window === "undefined") return () => {
259
+ };
260
+ window.addEventListener("online", callback);
261
+ window.addEventListener("offline", callback);
262
+ return () => {
263
+ window.removeEventListener("online", callback);
264
+ window.removeEventListener("offline", callback);
265
+ };
266
+ }
267
+ function getSnapshot() {
268
+ return typeof navigator !== "undefined" ? navigator.onLine : true;
269
+ }
270
+ function getServerSnapshot() {
271
+ return true;
272
+ }
273
+ function useOnlineStatus() {
274
+ return (0, import_react13.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
275
+ }
165
276
  // Annotate the CommonJS export names for ESM import in node:
166
277
  0 && (module.exports = {
167
278
  useClickOutside,
168
279
  useCopyToClipboard,
169
280
  useDebounce,
281
+ useFetch,
170
282
  useIntersectionObserver,
283
+ useKeyPress,
171
284
  useLocalStorage,
172
285
  useMediaQuery,
286
+ useOnlineStatus,
287
+ usePrevious,
288
+ useThrottle,
173
289
  useToggle,
174
290
  useWindowSize
175
291
  });
package/dist/index.mjs CHANGED
@@ -130,13 +130,124 @@ function useWindowSize() {
130
130
  }, []);
131
131
  return size;
132
132
  }
133
+
134
+ // src/hooks/useFetch.ts
135
+ import { useState as useState8, useEffect as useEffect6, useCallback as useCallback4, useRef } from "react";
136
+ function useFetch(url, options) {
137
+ const [state, setState] = useState8({
138
+ data: null,
139
+ error: null,
140
+ isLoading: !!url
141
+ });
142
+ const optionsRef = useRef(options);
143
+ optionsRef.current = options;
144
+ const fetchData = useCallback4(async () => {
145
+ if (!url) return;
146
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
147
+ try {
148
+ const res = await fetch(url, optionsRef.current);
149
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
150
+ const data = await res.json();
151
+ setState({ data, error: null, isLoading: false });
152
+ } catch (err) {
153
+ setState({ data: null, error: err, isLoading: false });
154
+ }
155
+ }, [url]);
156
+ useEffect6(() => {
157
+ fetchData();
158
+ }, [fetchData]);
159
+ return { ...state, refetch: fetchData };
160
+ }
161
+
162
+ // src/hooks/useThrottle.ts
163
+ import { useState as useState9, useEffect as useEffect7, useRef as useRef2 } from "react";
164
+ function useThrottle(value, delay) {
165
+ const [throttled, setThrottled] = useState9(value);
166
+ const lastUpdated = useRef2(Date.now());
167
+ const timeoutRef = useRef2(void 0);
168
+ useEffect7(() => {
169
+ const now = Date.now();
170
+ const elapsed = now - lastUpdated.current;
171
+ if (elapsed >= delay) {
172
+ setThrottled(value);
173
+ lastUpdated.current = now;
174
+ } else {
175
+ clearTimeout(timeoutRef.current);
176
+ timeoutRef.current = setTimeout(() => {
177
+ setThrottled(value);
178
+ lastUpdated.current = Date.now();
179
+ }, delay - elapsed);
180
+ }
181
+ return () => clearTimeout(timeoutRef.current);
182
+ }, [value, delay]);
183
+ return throttled;
184
+ }
185
+
186
+ // src/hooks/usePrevious.ts
187
+ import { useRef as useRef3, useEffect as useEffect8 } from "react";
188
+ function usePrevious(value) {
189
+ const ref = useRef3(void 0);
190
+ useEffect8(() => {
191
+ ref.current = value;
192
+ }, [value]);
193
+ return ref.current;
194
+ }
195
+
196
+ // src/hooks/useKeyPress.ts
197
+ import { useState as useState10, useEffect as useEffect9 } from "react";
198
+ function useKeyPress(targetKey) {
199
+ const [isPressed, setIsPressed] = useState10(false);
200
+ useEffect9(() => {
201
+ if (typeof window === "undefined") return;
202
+ const handleDown = (e) => {
203
+ if (e.key === targetKey) setIsPressed(true);
204
+ };
205
+ const handleUp = (e) => {
206
+ if (e.key === targetKey) setIsPressed(false);
207
+ };
208
+ window.addEventListener("keydown", handleDown);
209
+ window.addEventListener("keyup", handleUp);
210
+ return () => {
211
+ window.removeEventListener("keydown", handleDown);
212
+ window.removeEventListener("keyup", handleUp);
213
+ };
214
+ }, [targetKey]);
215
+ return isPressed;
216
+ }
217
+
218
+ // src/hooks/useOnlineStatus.ts
219
+ import { useSyncExternalStore } from "react";
220
+ function subscribe(callback) {
221
+ if (typeof window === "undefined") return () => {
222
+ };
223
+ window.addEventListener("online", callback);
224
+ window.addEventListener("offline", callback);
225
+ return () => {
226
+ window.removeEventListener("online", callback);
227
+ window.removeEventListener("offline", callback);
228
+ };
229
+ }
230
+ function getSnapshot() {
231
+ return typeof navigator !== "undefined" ? navigator.onLine : true;
232
+ }
233
+ function getServerSnapshot() {
234
+ return true;
235
+ }
236
+ function useOnlineStatus() {
237
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
238
+ }
133
239
  export {
134
240
  useClickOutside,
135
241
  useCopyToClipboard,
136
242
  useDebounce,
243
+ useFetch,
137
244
  useIntersectionObserver,
245
+ useKeyPress,
138
246
  useLocalStorage,
139
247
  useMediaQuery,
248
+ useOnlineStatus,
249
+ usePrevious,
250
+ useThrottle,
140
251
  useToggle,
141
252
  useWindowSize
142
253
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@allem-sdk/hooks",
3
- "version": "0.1.1",
4
- "description": "A collection of essential React hooks for modern applications",
3
+ "version": "0.1.3",
4
+ "description": "8 essential React hooks: useDebounce, useLocalStorage, useMediaQuery, useClickOutside, useToggle, useCopyToClipboard, useIntersectionObserver, useWindowSize. SSR-safe, TypeScript strict.",
5
5
  "license": "MIT",
6
6
  "author": "Ahmed Allem",
7
7
  "main": "./dist/index.js",
@@ -39,10 +39,18 @@
39
39
  "keywords": [
40
40
  "allem-sdk",
41
41
  "react",
42
- "hooks",
42
+ "react-hooks",
43
43
  "useDebounce",
44
+ "useLocalStorage",
44
45
  "useMediaQuery",
45
- "useLocalStorage"
46
+ "useClickOutside",
47
+ "useToggle",
48
+ "useCopyToClipboard",
49
+ "useIntersectionObserver",
50
+ "useWindowSize",
51
+ "ssr",
52
+ "typescript",
53
+ "nextjs"
46
54
  ],
47
55
  "publishConfig": {
48
56
  "access": "public"