@ventlio/tanstack-query 0.6.0 → 0.6.1

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.
@@ -1,3 +1,2 @@
1
1
  export { result } from './result';
2
- export * from './scrollToTop';
3
2
  export * from './timeFuncs';
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import 'url-search-params-polyfill';
2
- import require$$0, { useState, useMemo, useEffect, startTransition } from 'react';
2
+ import require$$0, { useState, useMemo, useEffect, startTransition, useCallback } from 'react';
3
3
  import { create } from 'zustand';
4
- import { useQueryClient, useQuery, useInfiniteQuery, useMutation } from '@tanstack/react-query';
4
+ import { useQueryClient, useMutation, useInfiniteQuery, useQuery } from '@tanstack/react-query';
5
5
  import lodashSet from 'lodash.set';
6
6
  import axios from 'axios';
7
7
 
@@ -888,13 +888,6 @@ function result(object, path, defaultValue) {
888
888
  return current;
889
889
  }
890
890
 
891
- const scrollToTop = () => {
892
- window.scrollTo({
893
- top: 0,
894
- behavior: 'smooth',
895
- });
896
- };
897
-
898
891
  function getDateInFuture(days) {
899
892
  // Create a new Date object
900
893
  const date = new Date();
@@ -1295,11 +1288,9 @@ function getAppFiles(body, fileSelectors = []) {
1295
1288
 
1296
1289
  const useDeleteRequest = (deleteOptions) => {
1297
1290
  const { baseUrl, headers } = deleteOptions ?? {};
1298
- const [requestPath, setRequestPath] = useState('');
1299
- const [options, setOptions] = useState();
1300
1291
  const { headerProvider } = useStore(bootStore);
1301
1292
  const [requestPayload, setRequestPayload] = useState();
1302
- const isFutureQueriesPaused = usePauseFutureRequests((state) => state.isFutureQueriesPaused);
1293
+ const isFutureMutationsPaused = usePauseFutureRequests((state) => state.isFutureMutationsPaused);
1303
1294
  const { API_URL, TIMEOUT } = useEnvironmentVariables();
1304
1295
  const storeHeaders = useHeaderStore((state) => state.headers);
1305
1296
  // Get headers from both the store and the headerProvider (if configured)
@@ -1307,74 +1298,55 @@ const useDeleteRequest = (deleteOptions) => {
1307
1298
  const providerHeaders = headerProvider ? headerProvider() : undefined;
1308
1299
  return { ...providerHeaders, ...storeHeaders };
1309
1300
  }, [storeHeaders, headerProvider]);
1310
- const sendRequest = async (res, rej, queryKey) => {
1311
- const [url] = queryKey;
1312
- const requestUrl = (url ?? requestPath);
1301
+ const sendRequest = async (path) => {
1313
1302
  const requestOptions = {
1314
- path: requestUrl,
1303
+ path,
1315
1304
  headers: { ...globalHeaders, ...headers },
1316
1305
  baseURL: baseUrl ?? API_URL,
1317
1306
  method: HttpMethod.DELETE,
1318
1307
  timeout: TIMEOUT,
1319
1308
  };
1320
- // let deleteResponse: IRequestError | IRequestSuccess<TResponse>;
1321
- // if (middleware) {
1322
- // // perform global middleware
1323
- // deleteResponse = await middleware(
1324
- // async (middlewareOptions) =>
1325
- // await makeRequest<TResponse>(
1326
- // middlewareOptions ? { ...requestOptions, ...middlewareOptions } : requestOptions
1327
- // ),
1328
- // {
1329
- // path: requestUrl,
1330
- // baseUrl: baseUrl ?? API_URL,
1331
- // }
1332
- // );
1333
- // } else {
1334
1309
  const deleteResponse = await makeRequest(requestOptions);
1335
- // }
1336
1310
  if (deleteResponse.status) {
1337
- res(deleteResponse);
1311
+ return deleteResponse;
1338
1312
  }
1339
1313
  else {
1340
- rej(deleteResponse);
1314
+ throw deleteResponse;
1341
1315
  }
1342
1316
  };
1343
- const query = useQuery({
1344
- queryKey: [requestPath, {}],
1345
- queryFn: ({ queryKey }) => new Promise((res, rej) => sendRequest(res, rej, queryKey)),
1346
- enabled: false,
1347
- ...options,
1317
+ // Use mutation instead of query for DELETE operations
1318
+ const mutation = useMutation({
1319
+ mutationFn: async ({ path }) => sendRequest(path),
1348
1320
  });
1349
- const updatedPathAsync = async (link) => {
1350
- return setRequestPath(link);
1351
- };
1352
- const setOptionsAsync = async (fetchOptions) => {
1353
- return setOptions(fetchOptions);
1354
- };
1355
- const destroy = async (link, internalDeleteOptions) => {
1356
- if (!isFutureQueriesPaused) {
1357
- // set enabled to be true for every delete
1358
- internalDeleteOptions = internalDeleteOptions
1359
- ? { ...internalDeleteOptions, queryKey: [link, {}], enabled: true }
1360
- : { queryKey: [link, {}], enabled: true };
1361
- await setOptionsAsync(internalDeleteOptions);
1362
- await updatedPathAsync(link);
1363
- return query.data;
1321
+ /**
1322
+ * Perform a DELETE request to the specified path
1323
+ * @param path - The API path to send the DELETE request to
1324
+ * @param options - Optional mutation options (onSuccess, onError, etc.)
1325
+ */
1326
+ const destroy = async (path, options) => {
1327
+ if (!isFutureMutationsPaused) {
1328
+ return mutation.mutateAsync({ path }, options);
1364
1329
  }
1365
1330
  else {
1366
- setRequestPayload({ link, internalDeleteOptions });
1331
+ setRequestPayload({ path, options });
1367
1332
  return undefined;
1368
1333
  }
1369
1334
  };
1335
+ // Resume paused requests when mutations are unpaused
1370
1336
  useEffect(() => {
1371
- if (!isFutureQueriesPaused && requestPayload) {
1372
- destroy(requestPayload.link, requestPayload.internalDeleteOptions);
1337
+ if (!isFutureMutationsPaused && requestPayload) {
1338
+ destroy(requestPayload.path, requestPayload.options);
1373
1339
  setRequestPayload(undefined);
1374
1340
  }
1375
1341
  // eslint-disable-next-line react-hooks/exhaustive-deps
1376
- }, [isFutureQueriesPaused]);
1377
- return { destroy, ...query, isLoading: query.isLoading || isFutureQueriesPaused };
1342
+ }, [isFutureMutationsPaused]);
1343
+ return {
1344
+ destroy,
1345
+ ...mutation,
1346
+ isLoading: mutation.isPending || isFutureMutationsPaused,
1347
+ // For backward compatibility - mutations don't have initial loading state
1348
+ isInitialLoading: false,
1349
+ };
1378
1350
  };
1379
1351
 
1380
1352
  const useGetInfiniteRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl, headers, }) => {
@@ -1497,7 +1469,6 @@ const useGetInfiniteRequest = ({ path, load = false, queryOptions, keyTracker, b
1497
1469
  */
1498
1470
  const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl, headers, paginationConfig, }) => {
1499
1471
  const [requestPath, setRequestPath] = useState(path);
1500
- const [options, setOptions] = useState(queryOptions);
1501
1472
  const [page, setPage] = useState(1);
1502
1473
  const { API_URL, TIMEOUT } = useEnvironmentVariables();
1503
1474
  const { middleware, pagination: globalPaginationConfig, headerProvider } = useStore(bootStore);
@@ -1509,19 +1480,17 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
1509
1480
  // Merge: store headers take precedence over provider headers
1510
1481
  return { ...providerHeaders, ...storeHeaders };
1511
1482
  }, [storeHeaders, headerProvider]);
1512
- const [requestPayload, setRequestPayload] = useState();
1513
1483
  const isFutureQueriesPaused = usePauseFutureRequests((state) => state.isFutureQueriesPaused);
1514
- let queryClient = useQueryClient();
1515
- // eslint-disable-next-line react-hooks/exhaustive-deps
1516
- queryClient = useMemo(() => queryClient, []);
1484
+ const queryClient = useQueryClient();
1517
1485
  // Merge global and local pagination config
1518
1486
  const pagination = useMemo(() => ({
1519
1487
  ...globalPaginationConfig,
1520
1488
  ...paginationConfig,
1521
1489
  }), [globalPaginationConfig, paginationConfig]);
1522
- const sendRequest = async (res, rej, queryKey) => {
1523
- const [url] = queryKey;
1524
- const requestUrl = (url ?? requestPath);
1490
+ /**
1491
+ * Core request function that makes the actual HTTP request
1492
+ */
1493
+ const executeRequest = useCallback(async (requestUrl) => {
1525
1494
  const requestOptions = {
1526
1495
  path: requestUrl,
1527
1496
  headers: { ...globalHeaders, ...headers },
@@ -1548,26 +1517,32 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
1548
1517
  getResponse = await makeRequest(requestOptions);
1549
1518
  }
1550
1519
  if (getResponse.status) {
1551
- res(getResponse);
1520
+ return getResponse;
1552
1521
  }
1553
1522
  else {
1554
- rej(getResponse);
1523
+ throw getResponse;
1555
1524
  }
1556
- };
1525
+ }, [globalHeaders, headers, baseUrl, API_URL, TIMEOUT, middleware]);
1526
+ // The declarative query - only runs when load is true
1557
1527
  const query = useQuery({
1558
1528
  queryKey: [requestPath, {}],
1559
- queryFn: ({ queryKey }) => new Promise((res, rej) => sendRequest(res, rej, queryKey)),
1560
- enabled: load && !isFutureQueriesPaused,
1561
- ...options,
1529
+ queryFn: async ({ queryKey }) => {
1530
+ const [url] = queryKey;
1531
+ return executeRequest(url);
1532
+ },
1533
+ // Only enable when load is explicitly true AND queries aren't paused
1534
+ enabled: load === true && !isFutureQueriesPaused,
1535
+ ...queryOptions,
1562
1536
  });
1537
+ // Update request path when prop changes
1563
1538
  useEffect(() => {
1564
- if (path) {
1539
+ if (path && path !== requestPath) {
1565
1540
  setRequestPath(path);
1566
1541
  }
1567
- }, [path]);
1542
+ }, [path, requestPath]);
1543
+ // Track query key for external reference
1568
1544
  useEffect(() => {
1569
1545
  if (keyTracker) {
1570
- // set expiration time for the tracker
1571
1546
  queryClient.setQueryDefaults([keyTracker], {
1572
1547
  staleTime: Infinity,
1573
1548
  });
@@ -1577,8 +1552,7 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
1577
1552
  /**
1578
1553
  * Extract pagination data from response using configured extractor
1579
1554
  */
1580
- const getPaginationData = (response) => {
1581
- // Use the configured pagination extractor or fall back to default
1555
+ const getPaginationData = useCallback((response) => {
1582
1556
  const extractPagination = pagination.extractPagination ||
1583
1557
  ((res) => {
1584
1558
  if ('pagination' in res.data) {
@@ -1587,90 +1561,90 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
1587
1561
  return undefined;
1588
1562
  });
1589
1563
  return extractPagination(response);
1590
- };
1564
+ }, [pagination.extractPagination]);
1565
+ /**
1566
+ * Construct a pagination URL using the configured builder
1567
+ */
1568
+ const constructPaginationLink = useCallback((link, pageNumber) => {
1569
+ const buildPaginationUrl = pagination.buildPaginationUrl ||
1570
+ ((url, targetPage) => {
1571
+ const [pathname, queryString] = url.split('?');
1572
+ const queryParams = new URLSearchParams(queryString || '');
1573
+ const pageParamName = pagination.pageParamName || 'page';
1574
+ queryParams.set(pageParamName, String(targetPage));
1575
+ return pathname + '?' + queryParams.toString();
1576
+ });
1577
+ return buildPaginationUrl(link, pageNumber);
1578
+ }, [pagination.buildPaginationUrl, pagination.pageParamName]);
1591
1579
  /**
1592
1580
  * Navigate to the next page if available
1593
1581
  */
1594
- const nextPage = () => {
1595
- // The linter thinks query.data is always falsy, but we know it can be defined after a successful query
1596
- // Let's restructure to avoid the conditional
1597
- const paginationData = query.data && getPaginationData(query.data);
1582
+ const nextPage = useCallback(() => {
1583
+ const data = query.data;
1584
+ if (!data)
1585
+ return;
1586
+ const paginationData = getPaginationData(data);
1598
1587
  if (!paginationData)
1599
1588
  return;
1600
1589
  if (paginationData.next_page !== paginationData.current_page &&
1601
1590
  paginationData.next_page > paginationData.current_page) {
1602
- setRequestPath(constructPaginationLink(requestPath, paginationData.next_page));
1591
+ const newPath = constructPaginationLink(requestPath, paginationData.next_page);
1592
+ setRequestPath(newPath);
1593
+ setPage(paginationData.next_page);
1603
1594
  }
1604
- };
1595
+ }, [query.data, getPaginationData, constructPaginationLink, requestPath]);
1605
1596
  /**
1606
1597
  * Navigate to the previous page if available
1607
1598
  */
1608
- const prevPage = () => {
1609
- // The linter thinks query.data is always falsy, but we know it can be defined after a successful query
1610
- // Let's restructure to avoid the conditional
1611
- const paginationData = query.data && getPaginationData(query.data);
1599
+ const prevPage = useCallback(() => {
1600
+ const data = query.data;
1601
+ if (!data)
1602
+ return;
1603
+ const paginationData = getPaginationData(data);
1612
1604
  if (!paginationData)
1613
1605
  return;
1614
1606
  if (paginationData.previous_page !== paginationData.current_page &&
1615
1607
  paginationData.previous_page < paginationData.current_page) {
1616
- setRequestPath(constructPaginationLink(requestPath, paginationData.previous_page));
1608
+ const newPath = constructPaginationLink(requestPath, paginationData.previous_page);
1609
+ setRequestPath(newPath);
1610
+ setPage(paginationData.previous_page);
1617
1611
  }
1618
- };
1612
+ }, [query.data, getPaginationData, constructPaginationLink, requestPath]);
1619
1613
  /**
1620
- * Construct a pagination URL using the configured builder
1614
+ * Navigate to a specific page
1621
1615
  */
1622
- const constructPaginationLink = (link, pageNumber) => {
1623
- // Use the configured pagination URL builder or fall back to default
1624
- const buildPaginationUrl = pagination.buildPaginationUrl ||
1625
- ((url, page) => {
1626
- const [pathname, queryString] = url.split('?');
1627
- const queryParams = new URLSearchParams(queryString || '');
1628
- const pageParamName = pagination.pageParamName || 'page';
1629
- const oldPage = Number(queryParams.get(pageParamName));
1630
- queryParams.set(pageParamName, String(page));
1631
- const newUrl = pathname + '?' + queryParams.toString();
1632
- // only update page when pagination number changed
1633
- if (oldPage !== pageNumber) {
1634
- setPage(pageNumber);
1635
- }
1636
- return newUrl;
1637
- });
1638
- return buildPaginationUrl(link, pageNumber);
1639
- };
1616
+ const gotoPage = useCallback((pageNumber) => {
1617
+ const newPath = constructPaginationLink(requestPath, pageNumber);
1618
+ setRequestPath(newPath);
1619
+ setPage(pageNumber);
1620
+ }, [constructPaginationLink, requestPath]);
1640
1621
  /**
1641
- * Navigate to a specific page
1622
+ * Imperative GET request - fetches data from a dynamic URL
1623
+ * Uses queryClient.fetchQuery for proper caching and deduplication
1624
+ *
1625
+ * @param url - The URL to fetch from
1626
+ * @param fetchOptions - Optional query options (staleTime, gcTime, etc.)
1627
+ * @returns Promise resolving to the response data
1642
1628
  */
1643
- const gotoPage = (pageNumber) => {
1644
- setRequestPath(constructPaginationLink(requestPath, pageNumber));
1645
- };
1646
- const updatedPathAsync = async (link) => {
1647
- startTransition(() => {
1648
- setRequestPath(link);
1629
+ const get = useCallback(async (url, fetchOptions) => {
1630
+ if (isFutureQueriesPaused) {
1631
+ throw new Error('Queries are currently paused');
1632
+ }
1633
+ // Use fetchQuery for imperative fetching - this properly handles caching
1634
+ const result = await queryClient.fetchQuery({
1635
+ queryKey: [url, {}],
1636
+ queryFn: () => executeRequest(url),
1637
+ staleTime: fetchOptions?.staleTime,
1638
+ gcTime: fetchOptions?.gcTime,
1649
1639
  });
1650
- };
1651
- const setOptionsAsync = async (fetchOptions) => {
1652
- startTransition(() => {
1653
- setOptions(fetchOptions);
1654
- });
1655
- };
1656
- const get = async (link, fetchOptions) => {
1657
- if (!isFutureQueriesPaused) {
1658
- await setOptionsAsync(fetchOptions);
1659
- await updatedPathAsync(link);
1660
- return query.data;
1661
- }
1662
- else {
1663
- setRequestPayload({ link, fetchOptions });
1664
- return undefined;
1665
- }
1666
- };
1667
- useEffect(() => {
1668
- if (!isFutureQueriesPaused && requestPayload) {
1669
- get(requestPayload.link, requestPayload.fetchOptions);
1670
- setRequestPayload(undefined);
1671
- }
1672
- // eslint-disable-next-line react-hooks/exhaustive-deps
1673
- }, [isFutureQueriesPaused]);
1640
+ return result;
1641
+ }, [queryClient, executeRequest, isFutureQueriesPaused]);
1642
+ /**
1643
+ * Refetch the current query with the existing path
1644
+ */
1645
+ const refetch = useCallback(() => {
1646
+ return query.refetch();
1647
+ }, [query]);
1674
1648
  return {
1675
1649
  ...query,
1676
1650
  isLoading: query.isLoading || isFutureQueriesPaused,
@@ -1680,10 +1654,11 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
1680
1654
  get,
1681
1655
  gotoPage,
1682
1656
  page,
1657
+ refetch,
1683
1658
  queryKey: [requestPath, {}],
1684
- // Add pagination data accessor - restructured to avoid linter error
1685
- getPaginationData: function () {
1686
- return query.data ? getPaginationData(query.data) : undefined;
1659
+ getPaginationData: () => {
1660
+ const data = query.data;
1661
+ return data ? getPaginationData(data) : undefined;
1687
1662
  },
1688
1663
  };
1689
1664
  };
@@ -1691,7 +1666,7 @@ const useGetRequest = ({ path, load = false, queryOptions, keyTracker, baseUrl,
1691
1666
  const usePatchRequest = ({ path, baseUrl, headers }) => {
1692
1667
  const { API_URL, TIMEOUT } = useEnvironmentVariables();
1693
1668
  const { uploadProgressPercent, onUploadProgress } = useUploadProgress();
1694
- const { context, headerProvider } = useStore(bootStore);
1669
+ const { headerProvider } = useStore(bootStore);
1695
1670
  const storeHeaders = useHeaderStore((state) => state.headers);
1696
1671
  // Get headers from both the store and the headerProvider (if configured)
1697
1672
  const globalHeaders = useMemo(() => {
@@ -1730,17 +1705,9 @@ const usePatchRequest = ({ path, baseUrl, headers }) => {
1730
1705
  const patchResponse = await makeRequest(requestOptions);
1731
1706
  // }
1732
1707
  if (patchResponse.status) {
1733
- // scroll to top after success
1734
- if (context !== 'app') {
1735
- scrollToTop();
1736
- }
1737
1708
  res(patchResponse);
1738
1709
  }
1739
1710
  else {
1740
- // scroll to top after error
1741
- if (context !== 'app') {
1742
- scrollToTop();
1743
- }
1744
1711
  rej(patchResponse);
1745
1712
  }
1746
1713
  };
@@ -1772,7 +1739,7 @@ const usePatchRequest = ({ path, baseUrl, headers }) => {
1772
1739
 
1773
1740
  const usePostRequest = ({ path, isFormData = false, baseUrl, headers, fileSelectors, }) => {
1774
1741
  const { API_URL, TIMEOUT } = useEnvironmentVariables();
1775
- const { context, headerProvider } = useStore(bootStore);
1742
+ const { headerProvider } = useStore(bootStore);
1776
1743
  const storeHeaders = useHeaderStore((state) => state.headers);
1777
1744
  // Get headers from both the store and the headerProvider (if configured)
1778
1745
  // headerProvider allows reading from cookies/localStorage synchronously
@@ -1822,17 +1789,9 @@ const usePostRequest = ({ path, isFormData = false, baseUrl, headers, fileSelect
1822
1789
  const postResponse = await makeRequest(requestOptions);
1823
1790
  // }
1824
1791
  if (postResponse.status) {
1825
- // scroll to top after success
1826
- if (context !== 'app') {
1827
- scrollToTop();
1828
- }
1829
1792
  res(postResponse);
1830
1793
  }
1831
1794
  else {
1832
- // scroll to top after error
1833
- if (context !== 'app') {
1834
- scrollToTop();
1835
- }
1836
1795
  rej(postResponse);
1837
1796
  }
1838
1797
  };
@@ -1861,5 +1820,5 @@ const usePostRequest = ({ path, isFormData = false, baseUrl, headers, fileSelect
1861
1820
  return { post, uploadProgressPercent, ...mutation, isLoading: mutation.isPending || isFutureMutationsPaused };
1862
1821
  };
1863
1822
 
1864
- export { ContentType, HttpMethod, axiosInstance, bootstrapQueryRequest, buildFormData, errorTransformer, executeMiddlewareChain, getDateInFuture, makeRequest, result, scrollToTop, successTransformer, useBaseUrlStore, useDeleteRequest, useEnvironmentVariables, useGetInfiniteRequest, useGetRequest, useHeaderStore, useKeyTrackerModel, usePatchRequest, usePauseFutureRequests, usePostRequest, useQueryHeaders, useQueryModel, useReactNativeEnv, useRefetchQuery, useUploadProgress };
1823
+ export { ContentType, HttpMethod, axiosInstance, bootstrapQueryRequest, buildFormData, errorTransformer, executeMiddlewareChain, getDateInFuture, makeRequest, result, successTransformer, useBaseUrlStore, useDeleteRequest, useEnvironmentVariables, useGetInfiniteRequest, useGetRequest, useHeaderStore, useKeyTrackerModel, usePatchRequest, usePauseFutureRequests, usePostRequest, useQueryHeaders, useQueryModel, useReactNativeEnv, useRefetchQuery, useUploadProgress };
1865
1824
  //# sourceMappingURL=index.mjs.map