vasuzex 2.3.12 → 2.3.13

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.
@@ -169,7 +169,13 @@ export function DataTable(props) {
169
169
  const [statusFilter, setStatusFilter] = React.useState(urlState.statusFilter);
170
170
  const [limit, setLimit] = React.useState(urlState.limit);
171
171
  const [columnSearch, setColumnSearch] = React.useState(urlState.columnSearch);
172
-
172
+ // Debounced version of columnSearch — API is only called once the user pauses typing
173
+ const [debouncedColumnSearch, setDebouncedColumnSearch] = React.useState(urlState.columnSearch);
174
+ const columnSearchDebounceRef = React.useRef(null);
175
+
176
+ // Abort controller for in-flight requests — cancelled whenever new fetch params arrive
177
+ const abortControllerRef = React.useRef(null);
178
+
173
179
  const [data, setData] = React.useState([]);
174
180
  const [loading, setLoading] = React.useState(false);
175
181
  const [totalPages, setTotalPages] = React.useState(1);
@@ -283,6 +289,22 @@ export function DataTable(props) {
283
289
  }
284
290
  }, [location?.search, hasReactRouter, loadStateFromURL]);
285
291
 
292
+ // Debounce columnSearch — user sees immediate input response, but API call waits 400ms
293
+ // Also syncs back if columnSearch is set programmatically (URL restore / browser back)
294
+ React.useEffect(() => {
295
+ if (columnSearchDebounceRef.current) {
296
+ clearTimeout(columnSearchDebounceRef.current);
297
+ }
298
+ columnSearchDebounceRef.current = setTimeout(() => {
299
+ setDebouncedColumnSearch(columnSearch);
300
+ }, 400);
301
+ return () => {
302
+ if (columnSearchDebounceRef.current) {
303
+ clearTimeout(columnSearchDebounceRef.current);
304
+ }
305
+ };
306
+ }, [columnSearch]);
307
+
286
308
  const handleStatusToggle = async (row) => {
287
309
  if (!toggleLink) return;
288
310
  try {
@@ -298,12 +320,19 @@ export function DataTable(props) {
298
320
  }
299
321
  };
300
322
 
301
- // Reset page to 1 when columnSearch changes
323
+ // Reset page to 1 when debouncedColumnSearch changes
302
324
  React.useEffect(() => {
303
325
  setPage(1);
304
- }, [columnSearch]);
326
+ }, [debouncedColumnSearch]);
305
327
 
306
328
  const fetchData = React.useCallback(async () => {
329
+ // Cancel any in-flight request before starting a new one
330
+ if (abortControllerRef.current) {
331
+ abortControllerRef.current.abort();
332
+ }
333
+ abortControllerRef.current = new AbortController();
334
+ const signal = abortControllerRef.current.signal;
335
+
307
336
  setLoading(true);
308
337
  try {
309
338
  const params = new URLSearchParams({
@@ -314,14 +343,14 @@ export function DataTable(props) {
314
343
  });
315
344
  if (statusFilter !== "all") params.append("isActive", statusFilter);
316
345
  if (search) params.append("search", search);
317
- // Add column search params
318
- Object.entries(columnSearch).forEach(([field, value]) => {
346
+ // Add debounced column search params
347
+ Object.entries(debouncedColumnSearch).forEach(([field, value]) => {
319
348
  if (value) params.append(`columnSearch[${field}]`, value);
320
349
  });
321
350
 
322
351
  // Properly append params to apiUrl (check if apiUrl already has query params)
323
352
  const separator = apiUrl.includes('?') ? '&' : '?';
324
- const result = await api.get(`${apiUrl}${separator}${params}`);
353
+ const result = await api.get(`${apiUrl}${separator}${params}`, { signal });
325
354
 
326
355
  // Handle nested data structure: result.data.data OR result.data.items
327
356
  const items = Array.isArray(result.data)
@@ -333,13 +362,16 @@ export function DataTable(props) {
333
362
  setTotalPages(pagination?.totalPages || 1);
334
363
  setTotalItems(pagination?.total || 0);
335
364
  } catch (err) {
365
+ // Ignore abort errors — they are expected when a newer request supersedes this one
366
+ if (err && err.name === 'AbortError') return;
367
+ if (err && err.code === 'ERR_CANCELED') return;
336
368
  setData([]);
337
369
  setTotalPages(1);
338
370
  setTotalItems(0);
339
371
  } finally {
340
372
  setLoading(false);
341
373
  }
342
- }, [api, apiUrl, page, sortBy, sortOrder, statusFilter, limit, search, columnSearch]);
374
+ }, [api, apiUrl, page, sortBy, sortOrder, statusFilter, limit, search, debouncedColumnSearch]);
343
375
 
344
376
  // Trigger fetchData for main params
345
377
  React.useEffect(() => {
@@ -360,6 +392,15 @@ export function DataTable(props) {
360
392
  }
361
393
  }, [refreshKey, fetchData]);
362
394
 
395
+ // Abort any in-flight request when the component unmounts
396
+ React.useEffect(() => {
397
+ return () => {
398
+ if (abortControllerRef.current) {
399
+ abortControllerRef.current.abort();
400
+ }
401
+ };
402
+ }, []);
403
+
363
404
  const handleSort = (field) => {
364
405
  if (sortBy === field) {
365
406
  setSortOrder((prev) => (prev === "asc" ? "desc" : "asc"));
package/jsconfig.json CHANGED
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "module": "esnext",
16
16
  "moduleResolution": "node",
17
+ "ignoreDeprecations": "6.0",
17
18
  "target": "es2020",
18
19
  "lib": ["es2020"],
19
20
  "allowSyntheticDefaultImports": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vasuzex",
3
- "version": "2.3.12",
3
+ "version": "2.3.13",
4
4
  "description": "Laravel-inspired framework for Node.js monorepos - V2 with optimized dependencies",
5
5
  "type": "module",
6
6
  "main": "./framework/index.js",