improve: use react-query for caching

This commit is contained in:
h z
2024-12-08 17:11:14 +00:00
parent a31cec7ef0
commit 0e6fd8409a
15 changed files with 640 additions and 353 deletions

View File

@@ -0,0 +1,56 @@
import {useQuery, useMutation, useQueryClient} from 'react-query';
import {fetch_} from "./request-utils";
import config from "../config";
export const useMarkdown = (id) => {
return useQuery(
["markdown", id],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/${id}`),
{
enabled: !!id,
});
};
export const useIndexMarkdown = (path_id) => {
const queryClient = useQueryClient();
return useQuery(
["index_markdown", path_id],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/get_index/${path_id}`),{
enabled: !!path_id,
onSuccess: (data) => {
if(data && data.id){
queryClient.setQueryData(["markdown", data.id], data);
}
}
});
};
export const useMarkdownsByPath = (pathId) => {
return useQuery(
["markdownsByPath", pathId],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/by_path/${pathId}`),
{
enabled: !!pathId
});
};
export const useSaveMarkdown = () => {
const queryClient = useQueryClient();
return useMutation(({id, data}) => {
const url = id
? `${config.BACKEND_HOST}/api/markdown/${id}`
: `${config.BACKEND_HOST}/api/markdown/`;
const method = id ? "PUT" : "POST";
return fetch_(url, {
method,
body: JSON.stringify(data),
})
},{
onSuccess: (res, variables) => {
queryClient.invalidateQueries(["markdownsByPath", variables.data.parent_id]);
queryClient.invalidateQueries(["markdown", variables.data.id]);
},
});
};

90
src/utils/path-queries.js Normal file
View File

@@ -0,0 +1,90 @@
import { useQuery, useMutation, useQueryClient } from "react-query";
import { fetch_ } from "./request-utils";
import config from "../config";
export const usePaths = (parent_id) => {
const queryClient = useQueryClient();
return useQuery(
["paths", parent_id],
() => fetch_(`${config.BACKEND_HOST}/api/path/parent/${parent_id}`),
{
enabled: !!parent_id,
onSuccess: (data) => {
if(data) {
for (const pth of data)
{
queryClient.setQueryData(["path", pth.id], pth);
}
}
}
}
);
}
export const usePath = (id) => {
const queryClient = useQueryClient();
const cachedData = queryClient.getQueryData(["path", id]);
return useQuery(
["path", id],
() => fetch_(`${config.BACKEND_HOST}/api/path/${id}`),
{
enabled: !!id,
onSuccess: (data) => {
console.log(`path ${id} - ${cachedData}` );
}
}
);
};
export const useCreatePath = () => {
const queryClient = useQueryClient();
return useMutation(
(data) => fetch_(`${config.BACKEND_HOST}/api/path/`, {
method: "POST",
body: JSON.stringify(data),
}),
{
onSuccess: (res, variables) => {
console.log(JSON.stringify(variables));
queryClient.invalidateQueries(["paths", variables.parent_id]);
},
}
);
};
export const useUpdatePath = () => {
const queryClient = useQueryClient();
return useMutation(
({ id, data }) => fetch_(`${config.BACKEND_HOST}/api/path/${id}`, {
method: "PATCH",
body: JSON.stringify(data),
}),
{
onSuccess: (res, variables) => {
queryClient.invalidateQueries(["paths", res.parent_id]);
queryClient.invalidateQueries(["path", variables.data.id]);
},
}
);
};
export const useDeletePath = () => {
const queryClient = useQueryClient();
return useMutation(
(id) => fetch_(`${config.BACKEND_HOST}/api/path/${id}`, {
method: "DELETE",
}),
{
onSuccess: () => {
queryClient.invalidateQueries("paths");
},
}
);
};

View File

@@ -0,0 +1,28 @@
export async function fetch_(url, init = {}) {
const token = localStorage.getItem("accessToken");
const headers = {
'Content-Type': 'application/json',
...(init.headers || {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
};
const response = await fetch(url, { ...init, headers });
if (response.status === 304) {
return Promise.reject(new Error("Not Modified"));
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const error = new Error(response.statusText);
error.data = errorData;
throw error;
}
if (response.status === 204) {
return null;
}
return response.json();
}

View File

@@ -1,95 +0,0 @@
import {data} from "react-router-dom";
const ongoingRequests = new Map();
function _default_hash_function(url, method, body){
const url_obj = new URL(url, window.location.origin);
const query_params = [...url_obj.searchParams.entries()]
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}=${value}`)
.join("&");
const normalized_url = `${url_obj.origin}${url_obj.pathname}${query_params ? "?" + query_params : ""}`;
const normalized_body = body
? JSON.stringify(
Object.keys(body)
.sort()
.reduce((acc, key) => {
acc[key] = body[key];
return acc;
}, {})
)
: "";
return `${method.toUpperCase()}:${normalized_url}:${normalized_body}`;
}
export async function fetch_(url, init = {}, init_options = {}){
const default_options = {
use_cache: true,
cache_key: _default_hash_function(url, init.method || "GET", init.body || null),
cache_expires: 60,
use_token: true,
};
const options = { ...default_options, ...init_options };
const token = options.use_token ? localStorage.getItem("accessToken") : null;
const request_options = {
...init,
headers: {
...(init.headers || {}),
...(token ? {Authorization: `Bearer ${token}`} : {}),
...(init.method && ['PUT', 'POST'].includes(init.method.toUpperCase())
? {'Content-Type': 'application/json'}
: {}),
}
};
if(options.use_cache && ongoingRequests.has(options.cache_key)){
return ongoingRequests.get(options.cache_key);
}
const now = Date.now();
const cached_data = localStorage.getItem(options.cache_key);
if(options.use_cache && cached_data){
const {data, timestamp} = JSON.parse(cached_data);
if(now - timestamp < options.cache_expires * 1000)
return data;
}
try {
const fetchPromise = fetch(url, request_options)
.then((response) => {
if(!response.ok) {
throw new Error(`RESPONSE_ERROR: ${response.status}`);
}
return response.json();
})
.then((data) => {
if (options.use_cache)
{
localStorage.setItem(
options.cache_key,
JSON.stringify({ data, timestamp: now })
);
ongoingRequests.delete(options.cache_key);
}
return data;
});
if(options.use_cache){
ongoingRequests.set(options.cache_key, fetchPromise);
}
return await fetchPromise;
}catch(error){
if(options.use_cache){
ongoingRequests.delete(options.cache_key);
}
throw error;
}
}