perf: avoid blocking page render on data refresh
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 41s

This commit is contained in:
Cauê Faleiros
2026-05-28 11:46:11 -03:00
parent 6dbc5ee190
commit f4cf4366ee
3 changed files with 36 additions and 17 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useCallback, useState, useEffect } from 'react';
import { Outlet, Link, useLocation } from 'react-router-dom'; import { Outlet, Link, useLocation } from 'react-router-dom';
import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, Loader2, LogOut, Megaphone } from 'lucide-react'; import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, Loader2, LogOut, Megaphone } from 'lucide-react';
import type { DateRange, OrderData, StockData } from '../types'; import type { DateRange, OrderData, StockData } from '../types';
@@ -32,19 +32,22 @@ const Layout = () => {
return saved ? Number(saved) : 0; return saved ? Number(saved) : 0;
}); });
const loadData = async (showLoading = false) => { const loadData = useCallback(async (showLoading = false) => {
if (showLoading) setIsLoading(true); if (showLoading) setIsLoading(true);
const [data, stock] = await Promise.all([fetchData(), fetchStock()]); try {
setOrdersData(data); const [data, stock] = await Promise.all([fetchData(), fetchStock()]);
setStockData(stock); setOrdersData(data);
if (showLoading) setIsLoading(false); setStockData(stock);
}; } finally {
if (showLoading) setIsLoading(false);
}
}, []);
useEffect(() => { useEffect(() => {
// The dashboard has to fetch its initial server state after mount. // The dashboard fetches its initial server state after mount without blocking route render.
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
void loadData(true); void loadData(false).finally(() => setIsLoading(false));
}, []); }, [loadData]);
useEffect(() => { useEffect(() => {
if (refreshInterval === 0) return; if (refreshInterval === 0) return;
@@ -54,7 +57,7 @@ const Layout = () => {
}, refreshInterval); }, refreshInterval);
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, [refreshInterval]); }, [loadData, refreshInterval]);
useEffect(() => { useEffect(() => {
localStorage.setItem('nexstar_refresh_interval', refreshInterval.toString()); localStorage.setItem('nexstar_refresh_interval', refreshInterval.toString());
@@ -146,13 +149,13 @@ const Layout = () => {
{/* Content Area */} {/* Content Area */}
<div className="flex-1 overflow-y-auto p-8 relative"> <div className="flex-1 overflow-y-auto p-8 relative">
{isLoading ? ( {isLoading && (
<div className="flex items-center justify-center h-full"> <div className="absolute right-8 top-8 z-10 flex items-center gap-2 rounded-xl border border-dark-border bg-dark-card px-3 py-2 text-sm font-semibold text-dark-muted shadow-sm">
<Loader2 className="w-8 h-8 text-brand-primary animate-spin" /> <Loader2 className="h-4 w-4 animate-spin text-brand-primary" />
</div> Atualizando dados
) : ( </div>
<Outlet context={{ dateRange, setDateRange, ordersData, stockData, refreshInterval, setRefreshInterval, loadData }} />
)} )}
<Outlet context={{ dateRange, setDateRange, ordersData, stockData, isDataLoading: isLoading, refreshInterval, setRefreshInterval, loadData }} />
</div> </div>
</main> </main>
</div> </div>

View File

@@ -17,6 +17,14 @@ const ClientDetails = () => {
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
}; };
if (!groupedOrders.length && ordersData.length === 0) {
return (
<div className="text-center py-12">
<p className="text-zinc-500 dark:text-dark-muted font-medium">Carregando cliente...</p>
</div>
);
}
if (!groupedOrders.length) { if (!groupedOrders.length) {
return ( return (
<div className="text-center py-12"> <div className="text-center py-12">

View File

@@ -43,6 +43,14 @@ const ProductDetails = () => {
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
}; };
if (!productInfo && ordersData.length === 0) {
return (
<div className="text-center py-12">
<p className="text-zinc-500 dark:text-dark-muted font-medium">Carregando produto...</p>
</div>
);
}
if (!productInfo) { if (!productInfo) {
return ( return (
<div className="text-center py-12"> <div className="text-center py-12">