163 lines
3.6 KiB
JavaScript
163 lines
3.6 KiB
JavaScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { api, fetcher as defaultFetcher } from "./api.js";
|
|
|
|
function getBaseKey(key) {
|
|
return Array.isArray(key) ? key : [key];
|
|
}
|
|
|
|
function getListKey(key) {
|
|
return [...getBaseKey(key), "list"];
|
|
}
|
|
|
|
function getItemKey(key, id) {
|
|
return [...getBaseKey(key), id];
|
|
}
|
|
|
|
function buildUrl(url, params) {
|
|
if (!params) return url;
|
|
|
|
const searchParams = new URLSearchParams();
|
|
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null && value !== "") {
|
|
searchParams.append(key, value);
|
|
}
|
|
});
|
|
|
|
const query = searchParams.toString();
|
|
|
|
return query ? `${url}?${query}` : url;
|
|
}
|
|
|
|
function resolveUrl(url, data) {
|
|
if (typeof url === "function") {
|
|
return url(data?.id, data);
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
function omitId(data = {}) {
|
|
const { id, ...rest } = data;
|
|
return rest;
|
|
}
|
|
|
|
function joinUrl(baseUrl, id) {
|
|
return `${baseUrl.replace(/\/$/, "")}/${id}`;
|
|
}
|
|
|
|
function toHookName(actionName) {
|
|
return `use${actionName.charAt(0).toUpperCase()}${actionName.slice(1)}`;
|
|
}
|
|
|
|
export function createApi({
|
|
key,
|
|
url,
|
|
actions = {},
|
|
axiosInstance = api,
|
|
fetcher = defaultFetcher,
|
|
}) {
|
|
const baseKey = getBaseKey(key);
|
|
const listKey = getListKey(key);
|
|
|
|
function useGet(params, options = {}) {
|
|
return useQuery({
|
|
queryKey: params ? [...listKey, params] : listKey,
|
|
queryFn: () => fetcher(buildUrl(url, params)),
|
|
...options,
|
|
});
|
|
}
|
|
|
|
function useItem(id, options = {}) {
|
|
return useQuery({
|
|
queryKey: getItemKey(key, id),
|
|
queryFn: () => fetcher(joinUrl(url, id)),
|
|
enabled: Boolean(id),
|
|
...options,
|
|
});
|
|
}
|
|
|
|
function useAction(actionName, options = {}) {
|
|
const queryClient = useQueryClient();
|
|
const action = actions[actionName];
|
|
|
|
if (!action) {
|
|
throw new Error(`Action "${actionName}" is not defined.`);
|
|
}
|
|
|
|
return useMutation({
|
|
mutationKey: [...baseKey, actionName],
|
|
|
|
mutationFn: async (data = {}) => {
|
|
const method = action.method || "post";
|
|
const requestUrl = resolveUrl(action.url || url, data);
|
|
|
|
const payload = action.body
|
|
? action.body(data)
|
|
: action.sendRawPayload
|
|
? data
|
|
: omitId(data);
|
|
|
|
const response =
|
|
method === "delete"
|
|
? await axiosInstance.delete(
|
|
requestUrl,
|
|
payload && Object.keys(payload).length > 0
|
|
? { data: payload }
|
|
: undefined,
|
|
)
|
|
: await axiosInstance[method](requestUrl, payload);
|
|
|
|
return response.data;
|
|
},
|
|
|
|
onSuccess: (...args) => {
|
|
const [responseData, variables] = args;
|
|
|
|
queryClient.invalidateQueries({
|
|
queryKey: listKey,
|
|
});
|
|
|
|
const id = variables?.id ?? responseData?.id;
|
|
|
|
if (id) {
|
|
queryClient.invalidateQueries({
|
|
queryKey: getItemKey(key, id),
|
|
});
|
|
}
|
|
|
|
const extraInvalidateKeys =
|
|
typeof action.invalidateKeys === "function"
|
|
? action.invalidateKeys(responseData, variables)
|
|
: action.invalidateKeys;
|
|
|
|
extraInvalidateKeys?.forEach((queryKey) => {
|
|
queryClient.invalidateQueries({ queryKey });
|
|
});
|
|
|
|
options.onSuccess?.(...args);
|
|
},
|
|
|
|
...options,
|
|
});
|
|
}
|
|
|
|
const actionHooks = Object.keys(actions).reduce((acc, actionName) => {
|
|
acc[toHookName(actionName)] = (options = {}) =>
|
|
useAction(actionName, options);
|
|
|
|
return acc;
|
|
}, {});
|
|
|
|
return {
|
|
key: baseKey,
|
|
listKey,
|
|
|
|
useGet,
|
|
useItem,
|
|
useAction,
|
|
|
|
...actionHooks,
|
|
};
|
|
}
|