@duongtrungnguyen/next-helper
Version:
Helper library for Next.js 15
493 lines • 16.3 kB
JavaScript
;
"use client";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var hooks_exports = {};
__export(hooks_exports, {
useInfiniteQuery: () => useInfiniteQuery,
useIsFetching: () => useIsFetching,
useMutation: () => useMutation,
usePaginatedQuery: () => usePaginatedQuery,
useQuery: () => useQuery,
useQueryClient: () => useQueryClient
});
module.exports = __toCommonJS(hooks_exports);
var import_react = require("react");
var import_query_client = require("./query-client");
var import_cache = require("./cache");
const retryFn = async (fn, retries, retryDelay) => {
let attempts = 0;
const execute = async () => {
try {
return await fn();
} catch (error) {
const maxRetries = typeof retries === "number" ? retries : retries === true ? 3 : 0;
if (attempts >= maxRetries) {
throw error;
}
attempts++;
const delay = typeof retryDelay === "function" ? retryDelay(attempts) : typeof retryDelay === "number" ? retryDelay : Math.min(1e3 * 2 ** attempts, 3e4);
await new Promise((resolve) => setTimeout(resolve, delay));
return execute();
}
};
return execute();
};
function useQuery(options) {
const {
queryKey,
queryFn,
enabled = true,
staleTime = 5 * 60 * 1e3,
// 5 minutes
cacheTime = 30 * 60 * 1e3,
// 30 minutes
retry = 3,
retryDelay = (attempt) => Math.min(1e3 * 2 ** attempt, 3e4),
onSuccess,
onError,
onSettled,
initialData,
refetchInterval = false,
refetchOnWindowFocus = true,
refetchOnMount = true,
refetchOnReconnect = true,
select = (data2) => data2
} = options;
const [data, setData] = (0, import_react.useState)(() => {
const cachedData = import_cache.queryCache.get(queryKey);
if (cachedData !== void 0) {
return select(cachedData);
}
if (initialData) {
const initialValue = typeof initialData === "function" ? initialData() : initialData;
if (initialValue !== void 0) {
import_cache.queryCache.set(queryKey, initialValue, staleTime, cacheTime);
return select(initialValue);
}
}
return void 0;
});
const [error, setError] = (0, import_react.useState)(null);
const [status, setStatus] = (0, import_react.useState)(
data !== void 0 ? "success" : "idle"
);
const [isFetching, setIsFetching] = (0, import_react.useState)(false);
const refetchIntervalRef = (0, import_react.useRef)(null);
const abortControllerRef = (0, import_react.useRef)(null);
const mountedRef = (0, import_react.useRef)(true);
const fetchData = (0, import_react.useCallback)(async () => {
if (!enabled) {
return data;
}
setIsFetching(true);
import_query_client.queryClient.setFetching(queryKey, true);
if (status === "idle") {
setStatus("loading");
}
abortControllerRef.current = new AbortController();
try {
const result = await retryFn(
() => {
var _a;
return queryFn({}, {
queryKey,
signal: (_a = abortControllerRef.current) == null ? void 0 : _a.signal
});
},
retry,
retryDelay
);
if (!mountedRef.current) return result;
import_cache.queryCache.set(queryKey, result, staleTime, cacheTime);
const selectedData = select(result);
setData(selectedData);
setError(null);
setStatus("success");
onSuccess == null ? void 0 : onSuccess(selectedData);
onSettled == null ? void 0 : onSettled(selectedData, null);
return selectedData;
} catch (err) {
if (!mountedRef.current) throw err;
const error2 = err instanceof Error ? err : new Error(String(err));
setError(error2);
setStatus("error");
onError == null ? void 0 : onError(error2);
onSettled == null ? void 0 : onSettled(void 0, error2);
throw error2;
} finally {
if (mountedRef.current) {
setIsFetching(false);
import_query_client.queryClient.setFetching(queryKey, false);
}
}
}, [
queryKey,
queryFn,
enabled,
staleTime,
cacheTime,
retry,
retryDelay,
onSuccess,
onError,
onSettled,
select,
status,
data
]);
(0, import_react.useEffect)(() => {
const unsubscribe = import_cache.queryCache.subscribe(queryKey, () => {
const cachedData = import_cache.queryCache.get(queryKey);
if (cachedData !== void 0) {
setData(select(cachedData));
setError(null);
setStatus("success");
}
});
return unsubscribe;
}, [queryKey, select]);
(0, import_react.useEffect)(() => {
mountedRef.current = true;
if (enabled && (refetchOnMount || import_cache.queryCache.isStale(queryKey))) {
fetchData().catch(() => {
});
}
return () => {
mountedRef.current = false;
if (refetchIntervalRef.current) {
clearInterval(refetchIntervalRef.current);
}
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, [enabled, fetchData, queryKey, refetchOnMount]);
(0, import_react.useEffect)(() => {
if (!refetchInterval || !enabled) {
return;
}
refetchIntervalRef.current = setInterval(() => {
if (document.visibilityState === "visible") {
fetchData().catch(() => {
});
}
}, refetchInterval);
return () => {
if (refetchIntervalRef.current) {
clearInterval(refetchIntervalRef.current);
}
};
}, [refetchInterval, fetchData, enabled]);
(0, import_react.useEffect)(() => {
if (!refetchOnWindowFocus || !enabled) {
return;
}
const handleFocus = () => {
if (import_cache.queryCache.isStale(queryKey)) {
fetchData().catch(() => {
});
}
};
window.addEventListener("focus", handleFocus);
return () => {
window.removeEventListener("focus", handleFocus);
};
}, [refetchOnWindowFocus, fetchData, queryKey, enabled]);
(0, import_react.useEffect)(() => {
if (!refetchOnReconnect || !enabled) {
return;
}
const handleOnline = () => {
fetchData().catch(() => {
});
};
window.addEventListener("online", handleOnline);
return () => {
window.removeEventListener("online", handleOnline);
};
}, [refetchOnReconnect, fetchData, enabled]);
return {
data,
error,
isLoading: status === "loading",
isError: status === "error",
isSuccess: status === "success",
isFetching,
refetch: fetchData,
status
};
}
function useMutation(options) {
const {
mutationFn,
onMutate,
onSuccess,
onError,
onSettled,
retry = 0,
retryDelay = (attempt) => Math.min(1e3 * 2 ** attempt, 3e4)
} = options;
const [data, setData] = (0, import_react.useState)(void 0);
const [error, setError] = (0, import_react.useState)(null);
const [status, setStatus] = (0, import_react.useState)("idle");
const reset = (0, import_react.useCallback)(() => {
setData(void 0);
setError(null);
setStatus("idle");
}, []);
const mutate = (0, import_react.useCallback)(
async (variables) => {
setStatus("loading");
setData(void 0);
setError(null);
let context;
try {
if (onMutate) {
context = await onMutate(variables);
}
const result = await retryFn(() => mutationFn(variables), retry, retryDelay);
setData(result);
setStatus("success");
if (onSuccess) {
await onSuccess(result, variables, context);
}
if (onSettled) {
await onSettled(result, null, variables, context);
}
return result;
} catch (err) {
const error2 = err instanceof Error ? err : new Error(String(err));
setError(error2);
setStatus("error");
if (onError) {
await onError(error2, variables, context);
}
if (onSettled) {
await onSettled(void 0, error2, variables, context);
}
throw error2;
}
},
[mutationFn, onMutate, onSuccess, onError, onSettled, retry, retryDelay]
);
return {
data,
error,
isLoading: status === "loading",
isError: status === "error",
isSuccess: status === "success",
isIdle: status === "idle",
reset,
mutate,
mutateAsync: mutate,
status
};
}
function useInfiniteQuery(options) {
const { queryKey, queryFn, getNextPageParam, getPreviousPageParam, initialPageParam = 0, ...queryOptions } = options;
const [pages, setPages] = (0, import_react.useState)([]);
const [pageParams, setPageParams] = (0, import_react.useState)([initialPageParam]);
const [isFetchingNextPage, setIsFetchingNextPage] = (0, import_react.useState)(false);
const [isFetchingPreviousPage, setIsFetchingPreviousPage] = (0, import_react.useState)(false);
const getPageQueryKey = (pageParam) => [...queryKey, { page: pageParam }];
const queryResult = useQuery({
...queryOptions,
queryKey: getPageQueryKey(initialPageParam),
queryFn: async (params, context) => {
const result = await queryFn({ ...params, pageParam: initialPageParam }, context);
return result;
},
onSuccess: (data) => {
var _a;
setPages([data]);
(_a = queryOptions.onSuccess) == null ? void 0 : _a.call(queryOptions, data);
}
});
const { refetch, ...rest } = queryResult;
const hasNextPage = pages.length > 0 && getNextPageParam(pages[pages.length - 1], pages) !== void 0;
const hasPreviousPage = pages.length > 0 && (getPreviousPageParam == null ? void 0 : getPreviousPageParam(pages[0], pages)) !== void 0;
const fetchNextPage = (0, import_react.useCallback)(async () => {
if (!hasNextPage || isFetchingNextPage) {
return pages;
}
setIsFetchingNextPage(true);
try {
const lastPage = pages[pages.length - 1];
const nextPageParam = getNextPageParam(lastPage, pages);
if (nextPageParam === void 0) {
return pages;
}
const pageQueryKey = getPageQueryKey(nextPageParam);
const cachedData = import_cache.queryCache.get(pageQueryKey);
if (cachedData) {
setPages([...pages, cachedData]);
setPageParams([...pageParams, nextPageParam]);
return [...pages, cachedData];
}
const newPageData = await queryFn({ pageParam: nextPageParam }, {
queryKey: pageQueryKey
});
import_cache.queryCache.set(pageQueryKey, newPageData);
const newPages = [...pages, newPageData];
setPages(newPages);
setPageParams([...pageParams, nextPageParam]);
return newPages;
} finally {
setIsFetchingNextPage(false);
}
}, [hasNextPage, isFetchingNextPage, pages, getNextPageParam, pageParams, queryFn, getPageQueryKey]);
const fetchPreviousPage = (0, import_react.useCallback)(async () => {
if (!hasPreviousPage || isFetchingPreviousPage || !getPreviousPageParam) {
return pages;
}
setIsFetchingPreviousPage(true);
try {
const firstPage = pages[0];
const previousPageParam = getPreviousPageParam(firstPage, pages);
if (previousPageParam === void 0) {
return pages;
}
const pageQueryKey = getPageQueryKey(previousPageParam);
const cachedData = import_cache.queryCache.get(pageQueryKey);
if (cachedData) {
setPages([cachedData, ...pages]);
setPageParams([previousPageParam, ...pageParams]);
return [cachedData, ...pages];
}
const newPageData = await queryFn({ pageParam: previousPageParam }, {
queryKey: pageQueryKey
});
import_cache.queryCache.set(pageQueryKey, newPageData);
const newPages = [newPageData, ...pages];
setPages(newPages);
setPageParams([previousPageParam, ...pageParams]);
return newPages;
} finally {
setIsFetchingPreviousPage(false);
}
}, [hasPreviousPage, isFetchingPreviousPage, pages, getPreviousPageParam, pageParams, queryFn, getPageQueryKey]);
const refetchAll = (0, import_react.useCallback)(async () => {
const newPages = [];
for (let i = 0; i < pageParams.length; i++) {
const pageParam = pageParams[i];
const pageQueryKey = getPageQueryKey(pageParam);
import_cache.queryCache.invalidate(pageQueryKey);
const newPageData = await queryFn({ pageParam }, {
queryKey: pageQueryKey
});
import_cache.queryCache.set(pageQueryKey, newPageData);
newPages.push(newPageData);
}
setPages(newPages);
return newPages;
}, [pageParams, queryFn]);
return {
...rest,
data: pages.length > 0 ? pages : void 0,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
refetch: refetchAll
};
}
function usePaginatedQuery(options) {
var _a, _b, _c;
const {
queryKey,
queryFn,
pageSize = 10,
pageIndex: initialPageIndex = 0,
keepPreviousData = true,
...queryOptions
} = options;
const [pageIndex, setPageIndex] = (0, import_react.useState)(initialPageIndex);
const paginatedQueryKey = [...queryKey, { pageIndex, pageSize }];
const previousDataRef = (0, import_react.useRef)(null);
const queryResult = useQuery({
...queryOptions,
queryKey: paginatedQueryKey,
queryFn: async (params, context) => {
const combinedParams = {
...params,
pagination: {
pageIndex,
pageSize
},
queryKey: context.queryKey,
signal: context.signal
};
return queryFn(combinedParams);
}
});
(0, import_react.useEffect)(() => {
if (queryResult.data && !queryResult.isLoading) {
previousDataRef.current = queryResult.data;
}
}, [queryResult.data, queryResult.isLoading]);
const data = queryResult.isLoading && keepPreviousData && previousDataRef.current ? previousDataRef.current.data : (_a = queryResult.data) == null ? void 0 : _a.data;
const totalCount = queryResult.isLoading && keepPreviousData && previousDataRef.current ? previousDataRef.current.totalCount : ((_b = queryResult.data) == null ? void 0 : _b.totalCount) || 0;
const pageCount = queryResult.isLoading && keepPreviousData && previousDataRef.current ? previousDataRef.current.pageCount : ((_c = queryResult.data) == null ? void 0 : _c.pageCount) || 0;
const canPreviousPage = pageIndex > 0;
const canNextPage = pageIndex < pageCount - 1;
const previousPage = (0, import_react.useCallback)(() => {
setPageIndex((old) => Math.max(0, old - 1));
}, []);
const nextPage = (0, import_react.useCallback)(() => {
setPageIndex((old) => Math.min(pageCount - 1, old + 1));
}, [pageCount]);
return {
...queryResult,
data,
totalCount,
pageCount,
pageIndex,
pageSize,
setPageIndex,
previousPage,
nextPage,
canPreviousPage,
canNextPage
};
}
function useQueryClient() {
return import_query_client.queryClient;
}
function useIsFetching() {
const [count, setCount] = (0, import_react.useState)(import_query_client.queryClient.isFetching());
(0, import_react.useEffect)(() => {
const interval = setInterval(() => {
setCount(import_query_client.queryClient.isFetching());
}, 100);
return () => clearInterval(interval);
}, []);
return count;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
useInfiniteQuery,
useIsFetching,
useMutation,
usePaginatedQuery,
useQuery,
useQueryClient
});
//# sourceMappingURL=hooks.js.map