fix(ui): optimize react state, performance, and ux logic
- Refactored useFilteredNodes to memoize logic and prevent re-rendering. - Optimized LeftPanel useMemo dependencies and HostChart O(N) traversal. - Fixed polling interval desync and added WebSocket throttling/React Strict Mode transport patch. - Fixed Graph layout dependencies and 'ghost' children chevrons on collapsed nodes. - Fixed RightPanel tab persistence UI bug and resolved React Hooks order crash. No remaining known issues from the UI analysis.
This commit is contained in:
31
src/App.tsx
31
src/App.tsx
@@ -86,10 +86,15 @@ function App() {
|
||||
const pollIntervalRef = useRef(pollInterval);
|
||||
pollIntervalRef.current = pollInterval;
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
const loadData = useCallback(async (isBackgroundPoll = false) => {
|
||||
if (isLoadingRef.current) return;
|
||||
|
||||
setIsLoading(true);
|
||||
// Only set loading state if there are no existing nodes (initial fresh load)
|
||||
// or if this is an explicit user refresh (not background poll).
|
||||
const isInitialLoad = useTopologyStore.getState().nodes.length === 0;
|
||||
if (isInitialLoad && !isBackgroundPoll) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/discover`, {
|
||||
@@ -184,18 +189,20 @@ function App() {
|
||||
}, [toggleCommandPalette]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
const isInitialLoad = useTopologyStore.getState().nodes.length === 0;
|
||||
loadData(!isInitialLoad); // Poll in background if we already have nodes
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(loadData, pollIntervalRef.current);
|
||||
// Rely on pollInterval from store state instead of ref
|
||||
const intervalId = setInterval(() => loadData(true), pollInterval);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [loadData]);
|
||||
}, [loadData, pollInterval]);
|
||||
|
||||
// --- WebSocket connection (websocket-engineer skill) ---
|
||||
useEffect(() => {
|
||||
const socket: Socket = ioClient(API_BASE_URL, {
|
||||
transports: ['websocket', 'polling'],
|
||||
transports: ['polling', 'websocket'],
|
||||
reconnectionDelay: 1000,
|
||||
reconnectionDelayMax: 5000,
|
||||
reconnectionAttempts: Infinity,
|
||||
@@ -214,7 +221,14 @@ function App() {
|
||||
});
|
||||
|
||||
// Listen for real-time topology updates
|
||||
|
||||
let lastWsUpdate = 0;
|
||||
socket.on('topology:update', (data: ApiDiscoveryResponse) => {
|
||||
// Throttle updates to max 1 per second to prevent UI freezes
|
||||
const now = Date.now();
|
||||
if (now - lastWsUpdate < 1000) return;
|
||||
lastWsUpdate = now;
|
||||
|
||||
if (data?.hosts) {
|
||||
const discoveredHosts: DiscoveredHost[] = data.hosts.map((h: ApiHost) => ({
|
||||
name: h.name,
|
||||
@@ -288,9 +302,10 @@ const Footer = memo(function Footer() {
|
||||
const pollIntervalRef = useRef(pollInterval);
|
||||
pollIntervalRef.current = pollInterval;
|
||||
|
||||
const formatTime = (date: Date | null) => {
|
||||
const formatTime = (date: Date | string | null) => {
|
||||
if (!date) return 'Never';
|
||||
return date.toLocaleTimeString();
|
||||
const d = new Date(date);
|
||||
return isNaN(d.getTime()) ? 'Never' : d.toLocaleTimeString();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user