staleTime

This commit is contained in:
nvrl committed 2026-06-15 20:01:34 +02:00
1 parent 6a8e7d53f9
commit 1620b24ff3
4 files changed
+30 -12

No files matched your search

+5
View File
@@ -74,6 +74,11 @@ Verify mutations `invalidateQueries`; consider optimistic insert so reviews appe
--- ---
## Implementation status ## Implementation status
- [x] Q2 — `useCategories()` query hook (`api/queries/useCategories.ts`, key
`queryKeys.categories()`, 30 min `staleTime`). Replaced raw
`api.get('categories/')` + `useState`/`useEffect` in `App.tsx` with the hook;
dropped the now-unused `api` import. Cached + shared across mounts. `tsc` +
ESLint (0 errors) clean.
- [x] R1 + R2 + Q1 — notif menu dedup + i18n + SPA nav. Extracted shared - [x] R1 + R2 + Q1 — notif menu dedup + i18n + SPA nav. Extracted shared
`<NotificationMenu>` (`components/layout/NotificationMenu.tsx`) + `useNotificationClick()` `<NotificationMenu>` (`components/layout/NotificationMenu.tsx`) + `useNotificationClick()`
hook (`hooks/useNotificationClick.ts`); `App.tsx` and `NavSidebar.tsx` now both render hook (`hooks/useNotificationClick.ts`); `App.tsx` and `NavSidebar.tsx` now both render
+2 -12
View File
@@ -16,7 +16,6 @@ import { GlassAppBar } from './components/ui';
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import NotificationsIcon from '@mui/icons-material/Notifications'; import NotificationsIcon from '@mui/icons-material/Notifications';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import api from './lib/api';
import Footer from './components/Footer'; import Footer from './components/Footer';
import CookieBanner from './components/CookieBanner'; import CookieBanner from './components/CookieBanner';
import { GlobalSearch } from './app/GlobalSearch'; import { GlobalSearch } from './app/GlobalSearch';
@@ -24,6 +23,7 @@ import { useAuth } from './hooks/useAuth';
import { useTheme } from './hooks/useTheme'; import { useTheme } from './hooks/useTheme';
import { useDrawerAnchor } from './hooks/useDrawerAnchor'; import { useDrawerAnchor } from './hooks/useDrawerAnchor';
import { NavSidebar, NAV_SIDEBAR_WIDTH } from './components/layout/NavSidebar'; import { NavSidebar, NAV_SIDEBAR_WIDTH } from './components/layout/NavSidebar';
import { useCategories } from './api/queries/useCategories';
import { NotificationMenu } from './components/layout/NotificationMenu'; import { NotificationMenu } from './components/layout/NotificationMenu';
import { RequireSuperuser } from './components/auth/RequireSuperuser'; import { RequireSuperuser } from './components/auth/RequireSuperuser';
import type { CatppuccinTheme } from './theme'; import type { CatppuccinTheme } from './theme';
@@ -61,20 +61,10 @@ const App: React.FC = () => {
const { anchor: mobileAnchor } = useDrawerAnchor(); const { anchor: mobileAnchor } = useDrawerAnchor();
const [notifAnchorEl, setNotifAnchorEl] = useState<null | HTMLElement>(null); const [notifAnchorEl, setNotifAnchorEl] = useState<null | HTMLElement>(null);
const [categories, setCategories] = useState<{ name: string; slug: string }[]>([]); const { data: categories = [] } = useCategories();
const isMobile = useMediaQuery(muiTheme.breakpoints.down('md')); const isMobile = useMediaQuery(muiTheme.breakpoints.down('md'));
const [drawerOpen, setDrawerOpen] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false);
useEffect(() => {
api
.get('categories/')
.then((res) => {
const data = Array.isArray(res.data) ? res.data : res.data.results || [];
setCategories(data);
})
.catch(() => setCategories([]));
}, []);
useEffect(() => { useEffect(() => {
document.body.style.backgroundColor = muiTheme.palette.background.default; document.body.style.backgroundColor = muiTheme.palette.background.default;
document.body.style.color = muiTheme.palette.text.primary; document.body.style.color = muiTheme.palette.text.primary;
+1
View File
@@ -4,6 +4,7 @@
* params (URL-stable, not the full args object). * params (URL-stable, not the full args object).
*/ */
export const queryKeys = { export const queryKeys = {
categories: () => ['categories'] as const,
dashboard: () => ['dashboard'] as const, dashboard: () => ['dashboard'] as const,
publicProfile: (username: string) => ['publicProfile', username] as const, publicProfile: (username: string) => ['publicProfile', username] as const,
publicProfileAll: () => ['publicProfile'] as const, publicProfileAll: () => ['publicProfile'] as const,
+22
View File
@@ -0,0 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import api from '../../lib/api';
import { queryKeys } from '../keys';
export interface Category {
name: string;
slug: string;
}
/**
* Nav categories. Rarely change, so cached aggressively — one fetch shared by
* the AppBar drawer and desktop sidebar instead of a raw `useEffect` per mount.
*/
export const useCategories = () =>
useQuery({
queryKey: queryKeys.categories(),
queryFn: async () => {
const res = await api.get<Category[] | { results: Category[] }>('categories/');
return Array.isArray(res.data) ? res.data : (res.data.results ?? []);
},
staleTime: 30 * 60_000,
});