From f4cf4366ee8f3dbd8846e95906a51526878f4402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Thu, 28 May 2026 11:46:11 -0300 Subject: [PATCH] perf: avoid blocking page render on data refresh --- src/components/Layout.tsx | 37 +++++++++++++++++++----------------- src/pages/ClientDetails.tsx | 8 ++++++++ src/pages/ProductDetails.tsx | 8 ++++++++ 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index e47adcd..0d82dfc 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useCallback, useState, useEffect } from 'react'; import { Outlet, Link, useLocation } from 'react-router-dom'; import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, Loader2, LogOut, Megaphone } from 'lucide-react'; import type { DateRange, OrderData, StockData } from '../types'; @@ -32,19 +32,22 @@ const Layout = () => { return saved ? Number(saved) : 0; }); - const loadData = async (showLoading = false) => { + const loadData = useCallback(async (showLoading = false) => { if (showLoading) setIsLoading(true); - const [data, stock] = await Promise.all([fetchData(), fetchStock()]); - setOrdersData(data); - setStockData(stock); - if (showLoading) setIsLoading(false); - }; + try { + const [data, stock] = await Promise.all([fetchData(), fetchStock()]); + setOrdersData(data); + setStockData(stock); + } finally { + if (showLoading) setIsLoading(false); + } + }, []); 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 - void loadData(true); - }, []); + void loadData(false).finally(() => setIsLoading(false)); + }, [loadData]); useEffect(() => { if (refreshInterval === 0) return; @@ -54,7 +57,7 @@ const Layout = () => { }, refreshInterval); return () => clearInterval(intervalId); - }, [refreshInterval]); + }, [loadData, refreshInterval]); useEffect(() => { localStorage.setItem('nexstar_refresh_interval', refreshInterval.toString()); @@ -146,13 +149,13 @@ const Layout = () => { {/* Content Area */}
- {isLoading ? ( -
- -
- ) : ( - + {isLoading && ( +
+ + Atualizando dados +
)} +
diff --git a/src/pages/ClientDetails.tsx b/src/pages/ClientDetails.tsx index c20120c..2735934 100644 --- a/src/pages/ClientDetails.tsx +++ b/src/pages/ClientDetails.tsx @@ -17,6 +17,14 @@ const ClientDetails = () => { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); }; + if (!groupedOrders.length && ordersData.length === 0) { + return ( +
+

Carregando cliente...

+
+ ); + } + if (!groupedOrders.length) { return (
diff --git a/src/pages/ProductDetails.tsx b/src/pages/ProductDetails.tsx index 7db70ae..00fca55 100644 --- a/src/pages/ProductDetails.tsx +++ b/src/pages/ProductDetails.tsx @@ -43,6 +43,14 @@ const ProductDetails = () => { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); }; + if (!productInfo && ordersData.length === 0) { + return ( +
+

Carregando produto...

+
+ ); + } + if (!productInfo) { return (