From b36b1d9eb02261eed614e8f83ac734441937207b Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Tue, 3 Feb 2026 22:03:09 +0100 Subject: [PATCH 1/9] Improves app performane, switched to tab lazy-loading - Lazy Tab Rendering - on nav-tab change, renders only the active page-tab to improve performance (20-30ms win on each tab) - Decreased default Trading Analytics transaction page size from 50 to 20 elements (over 300ms win on first load, i'll refactor it soon) - Financial Report now lazy-loading (another 50ms win for Trading Analytics tab) - AVG 35ms faster navtab switching, but still it's 100-150ms per swich, global refactor soon. --- src/pages/live_scraper/index.tsx | 2 +- src/pages/trade_messages/index.tsx | 2 +- src/pages/trading_analytics/Tabs/Transaction/index.tsx | 4 ++-- src/pages/trading_analytics/Tabs/Transaction/queries.ts | 5 +++-- src/pages/trading_analytics/Tabs/WFGDPR/index.tsx | 2 +- src/pages/trading_analytics/index.tsx | 2 +- src/pages/warframe_market/index.tsx | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/pages/live_scraper/index.tsx b/src/pages/live_scraper/index.tsx index 404371d3..c26635ef 100644 --- a/src/pages/live_scraper/index.tsx +++ b/src/pages/live_scraper/index.tsx @@ -51,7 +51,7 @@ export default function LiveScraperPage() { {tabs.map((tab) => ( - {tab.component(activeTab === tab.id)} + {activeTab === tab.id && tab.component(true)} ))} diff --git a/src/pages/trade_messages/index.tsx b/src/pages/trade_messages/index.tsx index dd7f9a70..b618387b 100644 --- a/src/pages/trade_messages/index.tsx +++ b/src/pages/trade_messages/index.tsx @@ -47,7 +47,7 @@ export default function TradeMessagesPage() { {tabs.map((tab) => ( - {tab.component(activeTab === tab.id)} + {activeTab === tab.id && tab.component(true)} ))} diff --git a/src/pages/trading_analytics/Tabs/Transaction/index.tsx b/src/pages/trading_analytics/Tabs/Transaction/index.tsx index 4e885804..4cfad5a4 100644 --- a/src/pages/trading_analytics/Tabs/Transaction/index.tsx +++ b/src/pages/trading_analytics/Tabs/Transaction/index.tsx @@ -30,7 +30,7 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { const [queryData, setQueryData] = useLocalStorage({ key: "transaction_query_key", getInitialValueInEffect: false, - defaultValue: { page: 1, limit: 50, sort_by: "created_at", sort_direction: "desc" }, + defaultValue: { page: 1, limit: 20, sort_by: "created_at", sort_direction: "desc" }, }); // Translate general @@ -64,7 +64,7 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { }, []); // Queries - const { paginationQuery, financialReportQuery, refetchQueries } = useQueries({ queryData, isActive }); + const { paginationQuery, financialReportQuery, refetchQueries } = useQueries({ queryData, isActive, loadFinancialReport: showReport }); const handleRefresh = () => { console.log("Refreshing transactions due to Tauri event"); refetchQueries(); diff --git a/src/pages/trading_analytics/Tabs/Transaction/queries.ts b/src/pages/trading_analytics/Tabs/Transaction/queries.ts index 291f4248..abe0313b 100644 --- a/src/pages/trading_analytics/Tabs/Transaction/queries.ts +++ b/src/pages/trading_analytics/Tabs/Transaction/queries.ts @@ -5,9 +5,10 @@ import api from "@api/index"; interface QueriesHooks { queryData: TauriTypes.TransactionControllerGetListParams; isActive?: boolean; + loadFinancialReport?: boolean; } -export const useQueries = ({ queryData, isActive }: QueriesHooks) => { +export const useQueries = ({ queryData, isActive, loadFinancialReport = false }: QueriesHooks) => { const getPaginationQuery = useQuery({ queryKey: ["get_transaction_pagination", queryData], queryFn: () => api.transaction.getPagination(queryData), @@ -18,7 +19,7 @@ export const useQueries = ({ queryData, isActive }: QueriesHooks) => { queryKey: ["get_transaction_financial_report", queryData], queryFn: () => api.transaction.getFinancialReport({ ...queryData, page: 1, limit: -1 }), retry: false, - enabled: isActive, + enabled: isActive && loadFinancialReport, }); const refetchQueries = () => { getPaginationQuery.refetch(); diff --git a/src/pages/trading_analytics/Tabs/WFGDPR/index.tsx b/src/pages/trading_analytics/Tabs/WFGDPR/index.tsx index 6a2b7f8d..a522a45f 100644 --- a/src/pages/trading_analytics/Tabs/WFGDPR/index.tsx +++ b/src/pages/trading_analytics/Tabs/WFGDPR/index.tsx @@ -91,7 +91,7 @@ export const WarframeGDPRParser = ({ isActive }: WarframeGDPRParserProps = {}) = {tabs.map((tab) => ( - {tab.component(isActive !== false)} + {activeTab === tab.id && tab.component(isActive !== false)} ))} diff --git a/src/pages/trading_analytics/index.tsx b/src/pages/trading_analytics/index.tsx index dee1ce66..a202ffed 100644 --- a/src/pages/trading_analytics/index.tsx +++ b/src/pages/trading_analytics/index.tsx @@ -59,7 +59,7 @@ export default function TradingAnalyticsPage() { {tabs.map((tab) => ( - {tab.component(activeTab === tab.id)} + {activeTab === tab.id && tab.component(true)} ))} diff --git a/src/pages/warframe_market/index.tsx b/src/pages/warframe_market/index.tsx index 0f80f576..3dae1d94 100644 --- a/src/pages/warframe_market/index.tsx +++ b/src/pages/warframe_market/index.tsx @@ -36,7 +36,7 @@ export default function WarframeMarketPage() { {tabs.map((tab) => ( - {tab.component(activeTab === tab.id)} + {activeTab === tab.id && tab.component(true)} ))} From b45bf53088aaf17b7eaba03a4734d8edc9ce006d Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Wed, 4 Feb 2026 00:49:43 +0100 Subject: [PATCH 2/9] Improves app performance - Lazy load routes to enhance performance - Prefetch routes on hover (up to 30ms win) - avg load time decreased to ~70ms from ~130 on route change --- src/components/Layouts/LogIn/LogInLayout.tsx | 8 ++ src/components/Layouts/Routes.tsx | 90 ++++++++++--------- .../NavbarMinimalColored.tsx | 12 ++- src/components/Layouts/routeLoaders.ts | 46 ++++++++++ 4 files changed, 111 insertions(+), 45 deletions(-) create mode 100644 src/components/Layouts/routeLoaders.ts diff --git a/src/components/Layouts/LogIn/LogInLayout.tsx b/src/components/Layouts/LogIn/LogInLayout.tsx index 98883a2c..ea2d0196 100644 --- a/src/components/Layouts/LogIn/LogInLayout.tsx +++ b/src/components/Layouts/LogIn/LogInLayout.tsx @@ -11,6 +11,7 @@ import { useAuthContext } from "@contexts/auth.context"; import { open } from "@tauri-apps/plugin-shell"; import { AddMetric } from "@api/index"; import { faWarframeMarket, facTradingAnalytics } from "@icons"; +import { prefetchRoute } from "../routeLoaders"; export function LogInLayout() { // States @@ -32,6 +33,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("home"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("home"), }, { align: "top", @@ -40,6 +42,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("live_scraper"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("liveScraper"), }, { align: "top", @@ -48,6 +51,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("warframe_market"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("warframeMarket"), }, { align: "top", @@ -66,6 +70,7 @@ export function LogInLayout() { ), onClick: (e: NavbarLinkProps) => handleNavigate(e), label: useTranslateNavBar("chat"), + onPrefetch: () => prefetchRoute("chat"), }, { align: "top", @@ -74,6 +79,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("trading_analytics"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("tradingAnalytics"), }, { align: "top", @@ -82,6 +88,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("trade_messages"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("tradeMessages"), }, { align: "top", @@ -99,6 +106,7 @@ export function LogInLayout() { icon: , label: useTranslateNavBar("about"), onClick: (e: NavbarLinkProps) => handleNavigate(e), + onPrefetch: () => prefetchRoute("about"), }, ]; // Effects diff --git a/src/components/Layouts/Routes.tsx b/src/components/Layouts/Routes.tsx index 519e8697..3a519783 100644 --- a/src/components/Layouts/Routes.tsx +++ b/src/components/Layouts/Routes.tsx @@ -1,6 +1,8 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; import { useAppContext } from "@contexts/app.context"; import { useAuthContext } from "@contexts/auth.context"; +import { lazy } from "react"; +import { routeLoaders } from "./routeLoaders"; // Layouts import { LogInLayout } from "./LogIn"; @@ -9,36 +11,38 @@ import { LogOutLayout } from "./LogOut"; // Permissions Gate import AuthenticatedGate from "../AuthenticatedGate"; +// Lazy loaded pages for code splitting + // Home Routes -import PHome from "@pages/home"; +const PHome = lazy(routeLoaders.home); // Auth Routes -import PLogin from "@pages/auth/login"; +const PLogin = lazy(routeLoaders.login); // Debug Routes -import PDebug from "@pages/debug"; +const PDebug = lazy(routeLoaders.debug); // Error Routes -import PError from "@pages/error"; +const PError = lazy(routeLoaders.error); // Banned Routes -import PBanned from "@pages/banned"; +const PBanned = lazy(routeLoaders.banned); // Live Scraper -import PLiveScraper from "@pages/live_scraper"; +const PLiveScraper = lazy(routeLoaders.liveScraper); // Trading Analytics -import TradingAnalyticsPage from "@pages/trading_analytics"; +const TradingAnalyticsPage = lazy(routeLoaders.tradingAnalytics); // Warframe Market -import PWarframeMarket from "@pages/warframe_market"; -import PWarframeMarketChat from "@pages/chat"; +const PWarframeMarket = lazy(routeLoaders.warframeMarket); +const PWarframeMarketChat = lazy(routeLoaders.chat); // Trade messages -import PTradeMessages from "@pages/trade_messages"; +const PTradeMessages = lazy(routeLoaders.tradeMessages); // About Page -import AboutPage from "@pages/about"; +const AboutPage = lazy(routeLoaders.about); export function AppRoutes() { const { app_error } = useAppContext(); @@ -59,42 +63,42 @@ export function AppRoutes() { return ( - - {!ShowErrorPage() && !IsUserBanned() && ( - <> - }> - }> - } /> + + {!ShowErrorPage() && !IsUserBanned() && ( + <> + }> + }> + } /> + - - }> - }> - } /> - - } /> + }> + }> + } /> + + } /> + + } /> + } /> + } /> + } /> + } /> + } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> - } /> + + )} + {ShowErrorPage() && ( + }> + } /> + + )} + {IsUserBanned() && ( + }> + } /> - - )} - {ShowErrorPage() && ( - }> - } /> - - )} - {IsUserBanned() && ( - }> - } /> - - )} - + )} + ); } diff --git a/src/components/Layouts/Shared/NavbarMinimalColored/NavbarMinimalColored.tsx b/src/components/Layouts/Shared/NavbarMinimalColored/NavbarMinimalColored.tsx index 2dfec3aa..f0ed8b39 100644 --- a/src/components/Layouts/Shared/NavbarMinimalColored/NavbarMinimalColored.tsx +++ b/src/components/Layouts/Shared/NavbarMinimalColored/NavbarMinimalColored.tsx @@ -12,13 +12,21 @@ export type NavbarLinkProps = { web?: boolean; hide?: boolean; onClick?(e: NavbarLinkProps): void; + onPrefetch?(): void; } function NavbarLink(props: NavbarLinkProps) { - const { icon: Icon, label, active, onClick } = props; + const { icon: Icon, label, active, onClick, onPrefetch } = props; return ( - { onClick && onClick(props); }} className={classes.link} data-active={active || undefined} data-rainbow-bg={true}> + { onClick && onClick(props); }} + onMouseEnter={() => { onPrefetch && onPrefetch(); }} + onFocus={() => { onPrefetch && onPrefetch(); }} + className={classes.link} + data-active={active || undefined} + data-rainbow-bg={true} + > {Icon} diff --git a/src/components/Layouts/routeLoaders.ts b/src/components/Layouts/routeLoaders.ts new file mode 100644 index 00000000..a4be9472 --- /dev/null +++ b/src/components/Layouts/routeLoaders.ts @@ -0,0 +1,46 @@ +type RouteLoader = () => Promise; + +export const routeLoaders = { + home: () => import("@pages/home"), + login: () => import("@pages/auth/login"), + debug: () => import("@pages/debug"), + error: () => import("@pages/error"), + banned: () => import("@pages/banned"), + liveScraper: () => import("@pages/live_scraper"), + tradingAnalytics: () => import("@pages/trading_analytics"), + warframeMarket: () => import("@pages/warframe_market"), + chat: () => import("@pages/chat"), + tradeMessages: () => import("@pages/trade_messages"), + about: () => import("@pages/about"), +} as const satisfies Record; + +export type RouteLoaderKey = keyof typeof routeLoaders; + +const prefetched = new Set(); + +export const prefetchRoute = (key: RouteLoaderKey) => { + if (prefetched.has(key)) return; + prefetched.add(key); + routeLoaders[key]().catch(() => { + prefetched.delete(key); + }); +}; + +export const prefetchRoutes = (keys: RouteLoaderKey[]) => { + keys.forEach(prefetchRoute); +}; + +export const prefetchLoggedInRoutes = () => { + prefetchRoutes([ + "home", + "liveScraper", + "tradingAnalytics", + "tradeMessages", + "warframeMarket", + "chat", + "about", + "login", + "error", + "banned", + ]); +}; From 8f97dba26a6062ae8e0e969cfd30e8de63cdd36b Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Wed, 4 Feb 2026 22:57:39 +0100 Subject: [PATCH 3/9] Improves context values - Context value memorization to prevent unnecessary re-renders --- src/contexts/app.context.tsx | 16 +++++++++++++--- src/contexts/auth.context.tsx | 6 ++++-- src/contexts/liveScraper.context.tsx | 6 ++++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/contexts/app.context.tsx b/src/contexts/app.context.tsx index 673440d9..ce19d799 100644 --- a/src/contexts/app.context.tsx +++ b/src/contexts/app.context.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, useContext, useEffect, useMemo, useState } from "react"; import api from "@api/index"; import { QuantframeApiTypes, ResponseError, TauriTypes } from "$types"; import { AuthContextProvider } from "./auth.context"; @@ -202,10 +202,20 @@ export function AppContextProvider({ children }: AppContextProviderProps) { console.error("Error playing sound:", error); }); }); - return () => {}; + return () => { }; }, []); + const contextValue = useMemo(() => ({ + settings, + alerts: alerts?.results || [], + app_info, + app_error: error, + checkForUpdates, + loading, + setLang + }), [settings, alerts?.results, app_info, error, checkForUpdates, loading, setLang]); + return ( - + {!loading && ( diff --git a/src/contexts/auth.context.tsx b/src/contexts/auth.context.tsx index cc86e1f6..55a81a34 100644 --- a/src/contexts/auth.context.tsx +++ b/src/contexts/auth.context.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, useContext, useEffect, useMemo, useState } from "react"; import { OffTauriDataEvent, OnTauriDataEvent } from "@api/index"; import { TauriTypes } from "$types"; import api from "@api/index"; @@ -50,5 +50,7 @@ export function AuthContextProvider({ children }: TauriContextProviderProps) { }; }, []); - return {children}; + const contextValue = useMemo(() => ({ user }), [user]); + + return {children}; } diff --git a/src/contexts/liveScraper.context.tsx b/src/contexts/liveScraper.context.tsx index 1af337c4..aebe08d3 100644 --- a/src/contexts/liveScraper.context.tsx +++ b/src/contexts/liveScraper.context.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, useContext, useEffect, useMemo, useState } from "react"; import { TauriTypes } from "$types"; import api from "../api"; import { invoke } from "@tauri-apps/api/core"; @@ -53,5 +53,7 @@ export function LiveScraperContextProvider({ children }: LiveScraperContextProvi .catch((e) => console.error("Error checking initialization:", e)); }, []); - return {children}; + const contextValue = useMemo(() => ({ is_running, message }), [is_running, message]); + + return {children}; } From eff65f8129b355b55c3ad959e44a7e881cf84a8d Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Wed, 4 Feb 2026 23:09:57 +0100 Subject: [PATCH 4/9] Adds navlinks memoization to improve performance - no more recreation of navlinks on each rerender --- src/components/Layouts/LogIn/LogInLayout.tsx | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/Layouts/LogIn/LogInLayout.tsx b/src/components/Layouts/LogIn/LogInLayout.tsx index ea2d0196..7d8b24ea 100644 --- a/src/components/Layouts/LogIn/LogInLayout.tsx +++ b/src/components/Layouts/LogIn/LogInLayout.tsx @@ -4,7 +4,7 @@ import { Outlet, useNavigate } from "react-router-dom"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBug, faEnvelope, faGlobe, faHome, faInfoCircle, faMessage } from "@fortawesome/free-solid-svg-icons"; import { useTranslateComponent } from "@hooks/useTranslate.hook"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { NavbarLinkProps, NavbarMinimalColored } from "@components/Layouts/Shared/NavbarMinimalColored"; import { Header } from "@components/Layouts/Shared/Header"; import { useAuthContext } from "@contexts/auth.context"; @@ -25,7 +25,16 @@ export function LogInLayout() { useTranslate(`navbar.${key}`, { ...context }, i18Key); // States const navigate = useNavigate(); - const links = [ + const handleNavigate = useCallback((link: NavbarLinkProps) => { + if (link.web) open(link.link, "_blank"); + else navigate(link.link); + + if (link.id == lastPage || !link.id) return; + setLastPage(link.id || ""); + AddMetric("active_page", link.id); + }, [navigate, lastPage]); + + const links = useMemo(() => [ { align: "top", id: "home", @@ -108,19 +117,12 @@ export function LogInLayout() { onClick: (e: NavbarLinkProps) => handleNavigate(e), onPrefetch: () => prefetchRoute("about"), }, - ]; + ], [user?.unread_messages, handleNavigate]); + // Effects useEffect(() => { if (user?.qf_banned || user?.wfm_banned) navigate("/error/banned"); - }, [user]); - const handleNavigate = (link: NavbarLinkProps) => { - if (link.web) open(link.link, "_blank"); - else navigate(link.link); - - if (link.id == lastPage || !link.id) return; - setLastPage(link.id || ""); - AddMetric("active_page", link.id); - }; + }, [user, navigate]); return ( Date: Wed, 4 Feb 2026 23:14:45 +0100 Subject: [PATCH 5/9] Improves ItemName rendering speed There was a small issue with unnecessary API calls, on each render of ItemName component calling to cached `tradableItems` and `weapons`, so if in table were 100 items, each item calling api 2x times = 200 API calls. - Created `cache.context.tsx` to load tradableItems and weapons once on app level - Changed `ItemName.tsx` to use cache context instead of app context --- .../DataDisplay/ItemName/ItemName.tsx | 14 ++---- src/contexts/app.context.tsx | 5 ++- src/contexts/cache.context.tsx | 44 +++++++++++++++++++ 3 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 src/contexts/cache.context.tsx diff --git a/src/components/DataDisplay/ItemName/ItemName.tsx b/src/components/DataDisplay/ItemName/ItemName.tsx index 189734df..44c2a975 100644 --- a/src/components/DataDisplay/ItemName/ItemName.tsx +++ b/src/components/DataDisplay/ItemName/ItemName.tsx @@ -3,8 +3,7 @@ import { ItemWithMeta } from "$types"; import { TextTranslate } from "../../Shared/TextTranslate"; import { useTranslateCommon } from "@hooks/useTranslate.hook"; import { DisplaySettings, GetItemDisplay, GetSubTypeDisplay } from "@utils/helper"; -import { useQuery } from "@tanstack/react-query"; -import api from "@api/index"; +import { useCacheContext } from "@contexts/cache.context"; import { memo } from "react"; import { faAmberStar, faCyanStar } from "@icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -27,15 +26,8 @@ export type ItemNameProps = { export const ItemName = memo(function ItemName({ color, size, hideQuantity, value, displaySettings }: ItemNameProps) { const theme = useMantineTheme(); - // Fetch data from rust side - const { data: tradableItems } = useQuery({ - queryKey: ["cache_items"], - queryFn: () => api.cache.getTradableItems(), - }); - const { data: weapons } = useQuery({ - queryKey: ["cache_riven_weapons"], - queryFn: () => api.cache.getRivenWeapons(), - }); + // Fetch data from cache context + const { tradableItems, weapons } = useCacheContext(); const GetQuantity = (): string | number => { if (!value) return ""; let quantity = 0; diff --git a/src/contexts/app.context.tsx b/src/contexts/app.context.tsx index ce19d799..f45ab95f 100644 --- a/src/contexts/app.context.tsx +++ b/src/contexts/app.context.tsx @@ -14,6 +14,7 @@ import { useTranslateCommon, useTranslateComponent, useTranslateContexts } from import { resolveResource } from "@tauri-apps/api/path"; import { readTextFile } from "@tauri-apps/plugin-fs"; import { LiveScraperContextProvider } from "./liveScraper.context"; +import { CacheContextProvider } from "./cache.context"; import { notifications } from "@mantine/notifications"; import { TextTranslate } from "@components/Shared/TextTranslate"; import { useTauriEvent } from "@hooks/useTauriEvent.hook"; @@ -219,7 +220,9 @@ export function AppContextProvider({ children }: AppContextProviderProps) { {!loading && ( - {children} + + {children} + )} diff --git a/src/contexts/cache.context.tsx b/src/contexts/cache.context.tsx new file mode 100644 index 00000000..3b9be4c1 --- /dev/null +++ b/src/contexts/cache.context.tsx @@ -0,0 +1,44 @@ +import { createContext, useContext, useMemo } from "react"; +import { useQuery } from "@tanstack/react-query"; +import api from "@api/index"; +import { TauriTypes } from "$types"; + +export type CacheContextProps = { + tradableItems: TauriTypes.CacheTradableItem[]; + weapons: TauriTypes.CacheRivenWeapon[]; + isLoading: boolean; +}; + +export type CacheContextProviderProps = { + children: React.ReactNode; +}; + +export const CacheContext = createContext({ + tradableItems: [], + weapons: [], + isLoading: true, +}); + +export const useCacheContext = () => useContext(CacheContext); + +export function CacheContextProvider({ children }: CacheContextProviderProps) { + const { data: tradableItems, isLoading: isLoadingItems } = useQuery({ + queryKey: ["cache_items"], + queryFn: () => api.cache.getTradableItems(), + staleTime: Infinity, // Cache forever - items rarely change + }); + + const { data: weapons, isLoading: isLoadingWeapons } = useQuery({ + queryKey: ["cache_riven_weapons"], + queryFn: () => api.cache.getRivenWeapons(), + staleTime: Infinity, // same here + }); + + const contextValue = useMemo(() => ({ + tradableItems: tradableItems || [], + weapons: weapons || [], + isLoading: isLoadingItems || isLoadingWeapons, + }), [tradableItems, weapons, isLoadingItems, isLoadingWeapons]); + + return {children}; +} From 5d330c371bee33380cf07554d6ee9f1b57505827 Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Wed, 4 Feb 2026 23:57:54 +0100 Subject: [PATCH 6/9] Improves list rendering performance - memoizes TransactionListItem, ChatMessage, WFMOrder, WFMAuction, and RivenPreview to reduce unnecessary re-renders - switches RivenPreview weapons source to cache context - also memorizes tabs --- .../DataDisplay/ChatMessage/ChatMessage.tsx | 8 ++++---- .../DataDisplay/RivenPreview/RivenPreview.tsx | 14 ++++++-------- .../TransactionListItem/TransactionListItem.tsx | 5 +++-- .../DataDisplay/WFMAuction/WFMAuction.tsx | 5 +++-- src/components/DataDisplay/WFMOrder/WFMOrder.tsx | 10 +++++----- src/pages/live_scraper/index.tsx | 5 +++-- src/pages/trade_messages/index.tsx | 5 +++-- src/pages/trading_analytics/index.tsx | 5 +++-- src/pages/warframe_market/index.tsx | 5 +++-- 9 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/components/DataDisplay/ChatMessage/ChatMessage.tsx b/src/components/DataDisplay/ChatMessage/ChatMessage.tsx index fca70c4e..3832b55d 100644 --- a/src/components/DataDisplay/ChatMessage/ChatMessage.tsx +++ b/src/components/DataDisplay/ChatMessage/ChatMessage.tsx @@ -1,8 +1,8 @@ +import { memo, useEffect, useState } from "react"; import { Alert, Avatar, Collapse, Group, Stack, Text, Tooltip } from "@mantine/core"; import { WFMarketTypes } from "$types/index"; import { WFMThumbnail } from "@api/index"; import classes from "./ChatMessage.module.css"; -import { useEffect, useState } from "react"; import dayjs from "dayjs"; import calendar from "dayjs/plugin/calendar"; dayjs.extend(calendar); @@ -14,7 +14,7 @@ export type ChatMessageProps = { msg: WFMarketTypes.ChatMessage; }; -export const ChatMessage = ({ user, msg, sender }: ChatMessageProps) => { +export const ChatMessage = memo(function ChatMessage({ user, msg, sender }: ChatMessageProps) { const position = sender ? "right" : "left"; const [msgDate, setMsgDate] = useState(""); const [opened, setOpen] = useState(false); @@ -24,7 +24,7 @@ export const ChatMessage = ({ user, msg, sender }: ChatMessageProps) => { if (dayjs().diff(date, "h") > 48) setMsgDate(dayjs(date).format("MMMM D, YYYY h:mm A")); else setMsgDate(dayjs(date).calendar()); - return () => {}; + return () => { }; }, [msg.send_date]); return ( @@ -56,4 +56,4 @@ export const ChatMessage = ({ user, msg, sender }: ChatMessageProps) => { ); -}; +}); diff --git a/src/components/DataDisplay/RivenPreview/RivenPreview.tsx b/src/components/DataDisplay/RivenPreview/RivenPreview.tsx index f7fa78e4..b7dd2588 100644 --- a/src/components/DataDisplay/RivenPreview/RivenPreview.tsx +++ b/src/components/DataDisplay/RivenPreview/RivenPreview.tsx @@ -1,14 +1,15 @@ +import { memo, useEffect, useState } from "react"; import { Box, Collapse, PaperProps, Text } from "@mantine/core"; import classes from "./RivenPreview.module.css"; import { RivenAttribute, WFMarketTypes } from "$types/index"; import { TauriTypes } from "$types"; -import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import api from "@api/index"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowsRotate } from "@fortawesome/free-solid-svg-icons"; import { useHover } from "@mantine/hooks"; import { getPolarityIcon } from "@icons"; +import { useCacheContext } from "@contexts/cache.context"; export type RivenPreviewProps = { riven: WFMarketTypes.Auction | TauriTypes.StockRiven; @@ -21,7 +22,7 @@ interface RivenAttributeWithUnits extends RivenAttribute { symbol: string; } -export function RivenPreview({ paperProps, riven }: RivenPreviewProps) { +export const RivenPreview = memo(function RivenPreview({ paperProps, riven }: RivenPreviewProps) { // State const { hovered, ref } = useHover(); const [weapon, setWeapon] = useState(undefined); @@ -31,11 +32,8 @@ export function RivenPreview({ paperProps, riven }: RivenPreviewProps) { const [mastery, setMastery] = useState(0); const [reRolls, setReRolls] = useState(0); const [rank, setRank] = useState(0); - // Fetch data from rust side - const { data: weapons } = useQuery({ - queryKey: ["cache_riven_weapons"], - queryFn: () => api.cache.getRivenWeapons(), - }); + // Fetch data from cache context + const { weapons } = useCacheContext(); const { data: allAttributes } = useQuery({ queryKey: ["cache_riven_attributes"], queryFn: () => api.cache.getRivenAttributes(), @@ -150,4 +148,4 @@ export function RivenPreview({ paperProps, riven }: RivenPreviewProps) { ); -} +}); diff --git a/src/components/DataDisplay/TransactionListItem/TransactionListItem.tsx b/src/components/DataDisplay/TransactionListItem/TransactionListItem.tsx index 33a6b58a..e2ec7190 100644 --- a/src/components/DataDisplay/TransactionListItem/TransactionListItem.tsx +++ b/src/components/DataDisplay/TransactionListItem/TransactionListItem.tsx @@ -1,3 +1,4 @@ +import { memo } from "react"; import { Group, Paper, Stack, Text } from "@mantine/core"; import { TauriTypes } from "$types"; import dayjs from "dayjs"; @@ -8,7 +9,7 @@ export type TransactionListItemProps = { orientation?: "horizontal" | "vertical"; }; -export function TransactionListItem({ transaction, orientation = "horizontal" }: TransactionListItemProps) { +export const TransactionListItem = memo(function TransactionListItem({ transaction, orientation = "horizontal" }: TransactionListItemProps) { return ( {orientation === "horizontal" && ( @@ -37,4 +38,4 @@ export function TransactionListItem({ transaction, orientation = "horizontal" }: )} ); -} +}); diff --git a/src/components/DataDisplay/WFMAuction/WFMAuction.tsx b/src/components/DataDisplay/WFMAuction/WFMAuction.tsx index 4df9aa83..2fb925a7 100644 --- a/src/components/DataDisplay/WFMAuction/WFMAuction.tsx +++ b/src/components/DataDisplay/WFMAuction/WFMAuction.tsx @@ -1,3 +1,4 @@ +import { memo } from "react"; import { Grid, Card, alpha, Group, Collapse } from "@mantine/core"; import { WFMarketTypes } from "$types/index"; import { useTranslateCommon, useTranslateComponent } from "@hooks/useTranslate.hook"; @@ -17,7 +18,7 @@ export type WFMAuctionProps = { overlayFooter?: React.ReactNode; }; -export function WFMAuction({ header, auction, overlayFooter, hideFooter }: WFMAuctionProps) { +export const WFMAuction = memo(function WFMAuction({ header, auction, overlayFooter, hideFooter }: WFMAuctionProps) { // State const { hovered, ref } = useHover(); // Translate general @@ -103,4 +104,4 @@ export function WFMAuction({ header, auction, overlayFooter, hideFooter }: WFMAu ); -} +}); diff --git a/src/components/DataDisplay/WFMOrder/WFMOrder.tsx b/src/components/DataDisplay/WFMOrder/WFMOrder.tsx index bc0c00f4..c04edbfc 100644 --- a/src/components/DataDisplay/WFMOrder/WFMOrder.tsx +++ b/src/components/DataDisplay/WFMOrder/WFMOrder.tsx @@ -1,3 +1,4 @@ +import { memo } from "react"; import { Paper, Stack, PaperProps, Group, Divider, Box, Avatar, Text, Image, Grid, Rating, useMantineTheme } from "@mantine/core"; import classes from "./WFMOrder.module.css"; import { WFMarketTypes } from "$types/index"; @@ -20,7 +21,7 @@ export type WFMOrderProps = { paperProps?: PaperProps; }; -export function WFMOrder({ show_border, paperProps, order, footer, show_user, display_style }: WFMOrderProps) { +export const WFMOrder = memo(function WFMOrder({ show_border, paperProps, order, footer, show_user, display_style }: WFMOrderProps) { const theme = useMantineTheme(); // Translate general @@ -156,9 +157,8 @@ export function WFMOrder({ show_border, paperProps, order, footer, show_user, di className={classes.userName} truncate style={{ - borderBottomColor: `var(--qf-user-status-${ - (order.user?.status.toString() || "offline") == "in_game" ? "ingame" : order.user?.status - })`, + borderBottomColor: `var(--qf-user-status-${(order.user?.status.toString() || "offline") == "in_game" ? "ingame" : order.user?.status + })`, borderBottom: "rem(3px) solid", }} > @@ -175,4 +175,4 @@ export function WFMOrder({ show_border, paperProps, order, footer, show_user, di )} ); -} +}); diff --git a/src/pages/live_scraper/index.tsx b/src/pages/live_scraper/index.tsx index c26635ef..711bebe4 100644 --- a/src/pages/live_scraper/index.tsx +++ b/src/pages/live_scraper/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Box, Container, Tabs } from "@mantine/core"; import { useTranslatePages } from "@hooks/useTranslate.hook"; import { ItemPanel, RivenPanel, WishListPanel } from "./Tabs"; @@ -13,7 +14,7 @@ export default function LiveScraperPage() { const useTranslateTabs = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`tabs.${key}`, { ...context }, i18Key); - const tabs = [ + const tabs = useMemo(() => [ { label: useTranslateTabs("item.title"), component: (isActive: boolean) => , @@ -29,7 +30,7 @@ export default function LiveScraperPage() { component: (isActive: boolean) => , id: "wish_list", }, - ]; + ], []); const [activeTab, setActiveTab] = useLocalStorage({ key: "live_scraper.active_tab", diff --git a/src/pages/trade_messages/index.tsx b/src/pages/trade_messages/index.tsx index b618387b..92957789 100644 --- a/src/pages/trade_messages/index.tsx +++ b/src/pages/trade_messages/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Tabs } from "@mantine/core"; import { useTranslatePages } from "@hooks/useTranslate.hook"; import { ItemPanel, RivenPanel, CustomPanel } from "./Tabs"; @@ -12,7 +13,7 @@ export default function TradeMessagesPage() { const useTranslateTabs = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`tabs.${key}`, { ...context }, i18Key); - const tabs = [ + const tabs = useMemo(() => [ { label: useTranslateTabs("item.title"), component: (isActive: boolean) => , @@ -29,7 +30,7 @@ export default function TradeMessagesPage() { id: "riven", isPremium: true, }, - ]; + ], []); const [activeTab, setActiveTab] = useLocalStorage({ key: "trade_messages_active_tab", diff --git a/src/pages/trading_analytics/index.tsx b/src/pages/trading_analytics/index.tsx index a202ffed..76e21388 100644 --- a/src/pages/trading_analytics/index.tsx +++ b/src/pages/trading_analytics/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Tabs } from "@mantine/core"; import { useTranslatePages } from "@hooks/useTranslate.hook"; import { TransactionPanel, ItemPanel, RivenPanel, UserPanel, WarframeGDPRParser } from "./Tabs"; @@ -12,7 +13,7 @@ export default function TradingAnalyticsPage() { const useTranslateTabs = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateForm(`tabs.${key}`, { ...context }, i18Key); - const tabs = [ + const tabs = useMemo(() => [ { label: useTranslateTabs("transaction.title"), component: (isActive: boolean) => , @@ -41,7 +42,7 @@ export default function TradingAnalyticsPage() { id: "wfgdpr", isPremium: false, }, - ]; + ], []); const [activeTab, setActiveTab] = useLocalStorage({ key: "trading_analytics_active_tab", diff --git a/src/pages/warframe_market/index.tsx b/src/pages/warframe_market/index.tsx index 3dae1d94..168ef29d 100644 --- a/src/pages/warframe_market/index.tsx +++ b/src/pages/warframe_market/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Container, Tabs } from "@mantine/core"; import { useTranslatePages } from "@hooks/useTranslate.hook"; import { OrderPanel } from "./Tabs/Orders"; @@ -12,14 +13,14 @@ export default function WarframeMarketPage() { useTranslatePages(`warframe_market.${key}`, { ...context }, i18Key); const useTranslateTabs = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslate(`tabs.${key}`, { ...context }, i18Key); - const tabs = [ + const tabs = useMemo(() => [ { label: useTranslateTabs("orders.title"), component: (isActive: boolean) => , id: "orders", }, { label: useTranslateTabs("auctions.title"), component: (isActive: boolean) => , id: "auctions" }, - ]; + ], []); const [activeTab, setActiveTab] = useLocalStorage({ key: "warframe_market.active_tab", defaultValue: tabs[0].id, From c55f2f023891cb62d89b5ab3226d264fa67cd973 Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Fri, 6 Feb 2026 14:20:28 +0100 Subject: [PATCH 7/9] Removes ineffective React.memo from components Need to stom my memo() minigun --- src/components/DataDisplay/ChatMessage/ChatMessage.tsx | 8 ++++---- .../TransactionListItem/TransactionListItem.tsx | 5 ++--- src/components/DataDisplay/WFMAuction/WFMAuction.tsx | 5 ++--- src/components/DataDisplay/WFMOrder/WFMOrder.tsx | 5 ++--- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/components/DataDisplay/ChatMessage/ChatMessage.tsx b/src/components/DataDisplay/ChatMessage/ChatMessage.tsx index 3832b55d..776c46f6 100644 --- a/src/components/DataDisplay/ChatMessage/ChatMessage.tsx +++ b/src/components/DataDisplay/ChatMessage/ChatMessage.tsx @@ -1,8 +1,8 @@ -import { memo, useEffect, useState } from "react"; import { Alert, Avatar, Collapse, Group, Stack, Text, Tooltip } from "@mantine/core"; import { WFMarketTypes } from "$types/index"; import { WFMThumbnail } from "@api/index"; import classes from "./ChatMessage.module.css"; +import { useEffect, useState } from "react"; import dayjs from "dayjs"; import calendar from "dayjs/plugin/calendar"; dayjs.extend(calendar); @@ -14,7 +14,7 @@ export type ChatMessageProps = { msg: WFMarketTypes.ChatMessage; }; -export const ChatMessage = memo(function ChatMessage({ user, msg, sender }: ChatMessageProps) { +export const ChatMessage = ({ user, msg, sender }: ChatMessageProps) => { const position = sender ? "right" : "left"; const [msgDate, setMsgDate] = useState(""); const [opened, setOpen] = useState(false); @@ -24,7 +24,7 @@ export const ChatMessage = memo(function ChatMessage({ user, msg, sender }: Chat if (dayjs().diff(date, "h") > 48) setMsgDate(dayjs(date).format("MMMM D, YYYY h:mm A")); else setMsgDate(dayjs(date).calendar()); - return () => { }; + return () => {}; }, [msg.send_date]); return ( @@ -56,4 +56,4 @@ export const ChatMessage = memo(function ChatMessage({ user, msg, sender }: Chat ); -}); +}; \ No newline at end of file diff --git a/src/components/DataDisplay/TransactionListItem/TransactionListItem.tsx b/src/components/DataDisplay/TransactionListItem/TransactionListItem.tsx index e2ec7190..33a6b58a 100644 --- a/src/components/DataDisplay/TransactionListItem/TransactionListItem.tsx +++ b/src/components/DataDisplay/TransactionListItem/TransactionListItem.tsx @@ -1,4 +1,3 @@ -import { memo } from "react"; import { Group, Paper, Stack, Text } from "@mantine/core"; import { TauriTypes } from "$types"; import dayjs from "dayjs"; @@ -9,7 +8,7 @@ export type TransactionListItemProps = { orientation?: "horizontal" | "vertical"; }; -export const TransactionListItem = memo(function TransactionListItem({ transaction, orientation = "horizontal" }: TransactionListItemProps) { +export function TransactionListItem({ transaction, orientation = "horizontal" }: TransactionListItemProps) { return ( {orientation === "horizontal" && ( @@ -38,4 +37,4 @@ export const TransactionListItem = memo(function TransactionListItem({ transacti )} ); -}); +} diff --git a/src/components/DataDisplay/WFMAuction/WFMAuction.tsx b/src/components/DataDisplay/WFMAuction/WFMAuction.tsx index 2fb925a7..4df9aa83 100644 --- a/src/components/DataDisplay/WFMAuction/WFMAuction.tsx +++ b/src/components/DataDisplay/WFMAuction/WFMAuction.tsx @@ -1,4 +1,3 @@ -import { memo } from "react"; import { Grid, Card, alpha, Group, Collapse } from "@mantine/core"; import { WFMarketTypes } from "$types/index"; import { useTranslateCommon, useTranslateComponent } from "@hooks/useTranslate.hook"; @@ -18,7 +17,7 @@ export type WFMAuctionProps = { overlayFooter?: React.ReactNode; }; -export const WFMAuction = memo(function WFMAuction({ header, auction, overlayFooter, hideFooter }: WFMAuctionProps) { +export function WFMAuction({ header, auction, overlayFooter, hideFooter }: WFMAuctionProps) { // State const { hovered, ref } = useHover(); // Translate general @@ -104,4 +103,4 @@ export const WFMAuction = memo(function WFMAuction({ header, auction, overlayFoo ); -}); +} diff --git a/src/components/DataDisplay/WFMOrder/WFMOrder.tsx b/src/components/DataDisplay/WFMOrder/WFMOrder.tsx index c04edbfc..fb6334fc 100644 --- a/src/components/DataDisplay/WFMOrder/WFMOrder.tsx +++ b/src/components/DataDisplay/WFMOrder/WFMOrder.tsx @@ -1,4 +1,3 @@ -import { memo } from "react"; import { Paper, Stack, PaperProps, Group, Divider, Box, Avatar, Text, Image, Grid, Rating, useMantineTheme } from "@mantine/core"; import classes from "./WFMOrder.module.css"; import { WFMarketTypes } from "$types/index"; @@ -21,7 +20,7 @@ export type WFMOrderProps = { paperProps?: PaperProps; }; -export const WFMOrder = memo(function WFMOrder({ show_border, paperProps, order, footer, show_user, display_style }: WFMOrderProps) { +export function WFMOrder({ show_border, paperProps, order, footer, show_user, display_style }: WFMOrderProps) { const theme = useMantineTheme(); // Translate general @@ -175,4 +174,4 @@ export const WFMOrder = memo(function WFMOrder({ show_border, paperProps, order, )} ); -}); +} From ccbf8b3abd4103d94727acaa4bb6816d811f3407 Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Sat, 7 Feb 2026 19:49:58 +0100 Subject: [PATCH 8/9] Optimises DataTable rendering to prevent nav lags - Replaced `isLoading` with `isFetching` to prevent unnecessary loading states during data refetching, ensuring smoother navigation and improved user experience. Updated all DataTables to use `isFetching` instead of `isLoading` for better performance during data refetching. `isLoading` is better for small DataTables, you can revert it to old behaviour if there no much items to load. (prevent loader flashes) --- src/pages/live_scraper/Tabs/Item/index.tsx | 4 ++-- src/pages/live_scraper/Tabs/Riven/index.tsx | 4 ++-- src/pages/live_scraper/Tabs/WishList/index.tsx | 4 ++-- src/pages/trade_messages/helpers/TradeEntryList.tsx | 2 +- src/pages/trading_analytics/Tabs/Item/index.tsx | 2 +- src/pages/trading_analytics/Tabs/Riven/index.tsx | 2 +- src/pages/trading_analytics/Tabs/Transaction/index.tsx | 8 ++++---- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/pages/live_scraper/Tabs/Item/index.tsx b/src/pages/live_scraper/Tabs/Item/index.tsx index b39cd5c9..3fb4c8f6 100644 --- a/src/pages/live_scraper/Tabs/Item/index.tsx +++ b/src/pages/live_scraper/Tabs/Item/index.tsx @@ -198,8 +198,8 @@ export const ItemPanel = ({ isActive }: ItemPanelProps = {}) => { }} mt={"md"} striped - fetching={paginationQuery.isLoading} - records={paginationQuery.data?.results || []} + fetching={paginationQuery.isFetching} + records={paginationQuery.isFetching ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/live_scraper/Tabs/Riven/index.tsx b/src/pages/live_scraper/Tabs/Riven/index.tsx index 7a44364d..2a90476e 100644 --- a/src/pages/live_scraper/Tabs/Riven/index.tsx +++ b/src/pages/live_scraper/Tabs/Riven/index.tsx @@ -194,8 +194,8 @@ export const RivenPanel = ({ isActive }: RivenPanelProps = {}) => { }} mt={"md"} striped - fetching={paginationQuery.isLoading} - records={paginationQuery.data?.results || []} + fetching={paginationQuery.isFetching} + records={paginationQuery.isFetching ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/live_scraper/Tabs/WishList/index.tsx b/src/pages/live_scraper/Tabs/WishList/index.tsx index 655d4285..5fae07c9 100644 --- a/src/pages/live_scraper/Tabs/WishList/index.tsx +++ b/src/pages/live_scraper/Tabs/WishList/index.tsx @@ -173,8 +173,8 @@ export const WishListPanel = ({ isActive }: WishListPanelProps = {}) => { }} mt={"md"} striped - fetching={paginationQuery.isLoading} - records={paginationQuery.data?.results || []} + fetching={paginationQuery.isFetching} + records={paginationQuery.isFetching ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/trade_messages/helpers/TradeEntryList.tsx b/src/pages/trade_messages/helpers/TradeEntryList.tsx index 7ed96e93..034f7463 100644 --- a/src/pages/trade_messages/helpers/TradeEntryList.tsx +++ b/src/pages/trade_messages/helpers/TradeEntryList.tsx @@ -237,7 +237,7 @@ export const TradeEntryList = ({ striped customLoader={} fetching={IsLoading()} - records={paginationQuery.data?.results || []} + records={IsLoading() ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.values.page, paginationQuery.data?.total_pages)} onPageChange={(page) => queryData.setFieldValue("page", page)} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/trading_analytics/Tabs/Item/index.tsx b/src/pages/trading_analytics/Tabs/Item/index.tsx index e7f809f1..5834c867 100644 --- a/src/pages/trading_analytics/Tabs/Item/index.tsx +++ b/src/pages/trading_analytics/Tabs/Item/index.tsx @@ -166,7 +166,7 @@ export const ItemPanel = ({ isActive }: ItemPanelProps = {}) => { striped customLoader={} fetching={IsLoading()} - records={paginationQuery.data?.results || []} + records={IsLoading() ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.values.page, paginationQuery.data?.total_pages)} onPageChange={(page) => queryData.setFieldValue("page", page)} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/trading_analytics/Tabs/Riven/index.tsx b/src/pages/trading_analytics/Tabs/Riven/index.tsx index dc660f23..a8d38e2d 100644 --- a/src/pages/trading_analytics/Tabs/Riven/index.tsx +++ b/src/pages/trading_analytics/Tabs/Riven/index.tsx @@ -154,7 +154,7 @@ export const RivenPanel = ({ isActive }: RivenPanelProps = {}) => { mt={"md"} striped fetching={IsLoading()} - records={paginationQuery.data?.results || []} + records={IsLoading() ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.values.page, paginationQuery.data?.total_pages)} onPageChange={(page) => queryData.setFieldValue("page", page)} totalRecords={paginationQuery.data?.total || 0} diff --git a/src/pages/trading_analytics/Tabs/Transaction/index.tsx b/src/pages/trading_analytics/Tabs/Transaction/index.tsx index 4cfad5a4..9bcfa86f 100644 --- a/src/pages/trading_analytics/Tabs/Transaction/index.tsx +++ b/src/pages/trading_analytics/Tabs/Transaction/index.tsx @@ -30,7 +30,7 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { const [queryData, setQueryData] = useLocalStorage({ key: "transaction_query_key", getInitialValueInEffect: false, - defaultValue: { page: 1, limit: 20, sort_by: "created_at", sort_direction: "desc" }, + defaultValue: { page: 1, limit: 50, sort_by: "created_at", sort_direction: "desc" }, }); // Translate general @@ -200,8 +200,8 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { className={`${classes.databaseTransactions} ${useHasAlert() ? classes.alert : ""} ${filterOpened ? classes.filterOpened : ""}`} mt={"md"} striped - fetching={paginationQuery.isLoading || calculateTaxMutation.isPending} - records={paginationQuery.data?.results || []} + fetching={paginationQuery.isFetching || calculateTaxMutation.isPending} + records={paginationQuery.isFetching ? [] : (paginationQuery.data?.results || [])} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0} @@ -367,7 +367,7 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { className={`${classes.databaseTradingPartners} ${useHasAlert() ? classes.alert : ""} ${filterOpened ? classes.filterOpened : ""}`} mt={"md"} striped - fetching={paginationQuery.isLoading || calculateTaxMutation.isPending} + fetching={paginationQuery.isFetching || calculateTaxMutation.isPending} records={financialReportQuery.data?.properties.trading_partners || []} idAccessor={"properties.user"} // define columns From fe80aed5b0fc534461064bf6faef62b427aec293 Mon Sep 17 00:00:00 2001 From: Yurii-IvoryFace Date: Sat, 7 Feb 2026 21:15:36 +0100 Subject: [PATCH 9/9] Adds Progressive Rendering for Transaction DataTable - it fixes UI freezing when trying to change page, allows to interupt DataTable loading There is still issue when DataTable loaded (after preloader starts to fade), if user tries to change page in this time he will encounter UI freezing up to 100-150ms D: Fix for that is Chunk Rendering or Virtualization, but it is a overkill? --- .../Tabs/Transaction/index.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/pages/trading_analytics/Tabs/Transaction/index.tsx b/src/pages/trading_analytics/Tabs/Transaction/index.tsx index 9bcfa86f..b1b1e75b 100644 --- a/src/pages/trading_analytics/Tabs/Transaction/index.tsx +++ b/src/pages/trading_analytics/Tabs/Transaction/index.tsx @@ -1,6 +1,6 @@ import { Box, Grid, Group, NumberFormatter, Paper, Table, Text, Title } from "@mantine/core"; import { useLocalStorage } from "@mantine/hooks"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useTransition } from "react"; import { TauriTypes } from "$types"; import { useQueries } from "./queries"; import { useTauriEvent } from "@hooks/useTauriEvent.hook"; @@ -58,6 +58,10 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { const [filterOpened, setFilterOpened] = useState(false); const [canExport, setCanExport] = useState(false); + // Progressive rendering === allows navigation during table render (without it ui freezes until table loads) + const [isPending, startTransition] = useTransition(); + const [displayedRecords, setDisplayedRecords] = useState([]); + // Check permissions for export on mount useEffect(() => { HasPermission(TauriTypes.PermissionsFlags.EXPORT_DATA).then((res) => setCanExport(res)); @@ -94,6 +98,17 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { // Use the custom hook for Tauri events useTauriEvent(TauriTypes.Events.RefreshTransactions, handleRefresh, []); + // Progressive rendering + useEffect(() => { + if (paginationQuery.isFetching) { + setDisplayedRecords([]); + } else if (paginationQuery.data?.results) { + startTransition(() => { + setDisplayedRecords(paginationQuery.data?.results || []); + }); + } + }, [paginationQuery.isFetching, paginationQuery.data?.results]); + return ( {" "} @@ -200,8 +215,8 @@ export const TransactionPanel = ({ isActive }: TransactionPanelProps = {}) => { className={`${classes.databaseTransactions} ${useHasAlert() ? classes.alert : ""} ${filterOpened ? classes.filterOpened : ""}`} mt={"md"} striped - fetching={paginationQuery.isFetching || calculateTaxMutation.isPending} - records={paginationQuery.isFetching ? [] : (paginationQuery.data?.results || [])} + fetching={paginationQuery.isFetching || isPending || calculateTaxMutation.isPending} + records={displayedRecords} page={getSafePage(queryData.page, paginationQuery.data?.total_pages)} onPageChange={(page) => setQueryData((prev) => ({ ...prev, page }))} totalRecords={paginationQuery.data?.total || 0}