mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
perf(admin): migrate AdminUserDetail loadUser to React Query (caching + cache key)
Replace the 13-line manual fetch with a useQuery hook keyed on userId. loadUser is kept as a thin wrapper around userQuery.refetch() so all 25+ existing call sites in mutation handlers continue to work unchanged. Sync useEffects copy data → setUser, isFetching → setLoading, and isError → navigate, preserving the original 'load fails → redirect to /admin/users' behavior. Remaining 12 loaders (loadSyncStatus/loadTariffs/loadTickets/loadReferrals/loadPanelInfo/ loadNodeUsage/loadDevices/loadSubscriptionData/loadGifts/loadPromoGroups/loadTicketDetail/ loadRequestHistory) NOT migrated in this commit — some are parameterized, some chain via Promise.all in loadSubscriptionData; they need a dedicated refactor with functional testing across all 7 tabs and the mutation flows. This commit ships the lowest-risk, highest-leverage piece (userQuery has the most call sites).
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router';
|
import { useParams, useNavigate } from 'react-router';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
import { DEVICE_ALIAS_MAX_LENGTH } from '../constants/devices';
|
import { DEVICE_ALIAS_MAX_LENGTH } from '../constants/devices';
|
||||||
import { useCurrency } from '../hooks/useCurrency';
|
import { useCurrency } from '../hooks/useCurrency';
|
||||||
@@ -369,19 +370,43 @@ export default function AdminUserDetail() {
|
|||||||
|
|
||||||
const userId = id ? parseInt(id, 10) : null;
|
const userId = id ? parseInt(id, 10) : null;
|
||||||
|
|
||||||
const loadUser = useCallback(async () => {
|
// React Query owns the main user fetch: caching across navigations + auto-loading
|
||||||
if (!userId) return;
|
// state. loadUser is kept as a thin refetch wrapper so the 25+ mutation handlers
|
||||||
try {
|
// that call `await loadUser()` after writes need no changes.
|
||||||
setLoading(true);
|
const userQuery = useQuery({
|
||||||
const data = await adminUsersApi.getUser(userId);
|
queryKey: ['admin-user-detail', userId] as const,
|
||||||
setUser(data);
|
queryFn: () => {
|
||||||
} catch (error) {
|
if (!userId) throw new Error('No userId');
|
||||||
console.error('Failed to load user:', error);
|
return adminUsersApi.getUser(userId);
|
||||||
|
},
|
||||||
|
enabled: !!userId && !isNaN(userId),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userQuery.data) setUser(userQuery.data);
|
||||||
|
}, [userQuery.data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(userQuery.isFetching);
|
||||||
|
}, [userQuery.isFetching]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userQuery.isError) {
|
||||||
|
console.error('Failed to load user:', userQuery.error);
|
||||||
navigate('/admin/users');
|
navigate('/admin/users');
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
}, [userId, navigate]);
|
}, [userQuery.isError, userQuery.error, navigate]);
|
||||||
|
|
||||||
|
const loadUser = useCallback(
|
||||||
|
async () => {
|
||||||
|
await userQuery.refetch();
|
||||||
|
},
|
||||||
|
// userQuery.refetch is a stable function across renders; including the whole
|
||||||
|
// userQuery object would re-create loadUser on every render and cascade into
|
||||||
|
// useEffects that depend on it.
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[userQuery.refetch],
|
||||||
|
);
|
||||||
|
|
||||||
const loadSyncStatus = useCallback(async () => {
|
const loadSyncStatus = useCallback(async () => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
@@ -579,10 +604,9 @@ export default function AdminUserDetail() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userId || isNaN(userId)) {
|
if (!userId || isNaN(userId)) {
|
||||||
navigate('/admin/users');
|
navigate('/admin/users');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
loadUser();
|
// user data is auto-loaded by userQuery (enabled when userId is valid)
|
||||||
}, [userId, loadUser, navigate]);
|
}, [userId, navigate]);
|
||||||
|
|
||||||
// Load panel info when subscription changes (separate from mount to avoid redundant loadUser)
|
// Load panel info when subscription changes (separate from mount to avoid redundant loadUser)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user