perf(dashboard): cut re-render cost + lazy-load ApexCharts#1109
Open
TaprootFreak wants to merge 5 commits into
Open
perf(dashboard): cut re-render cost + lazy-load ApexCharts#1109TaprootFreak wants to merge 5 commits into
TaprootFreak wants to merge 5 commits into
Conversation
…hanged The 60s auto-refresh in the financial overview always called setLatestBalance and setLogEntries with fresh response objects, producing new array/object references on every poll. That forced React to re-render the chart subtree and triggered ApexCharts to redo SVG paints even when the payload was identical. Add two pure equality helpers and switch the setters to the functional form so identity is preserved when nothing changed: - sameLatestBalance compares the deterministic backend timestamp and the byType length as a cheap safety net. - sameLogEntries leverages the append-only nature of the log and compares length plus the trailing timestamp. Includes unit tests for both helpers covering reference, undefined, empty, length, and tail-timestamp edge cases.
…puts Two micro-optimizations that fix invalidated memos in the financial overview render path: 1. useDashboard returned a useMemo'd object, but the five async functions were declared in the hook body and therefore recreated on every render. Move them inside the useMemo so their identity follows the call ref from useApi rather than every parent render. 2. SimpleBarChart computed categories, values and colors directly in the function body and then fed them as useMemo deps for options and series. New arrays each render meant the memos were effectively useless. Wrap the three arrays in a single useMemo keyed on data, so downstream memos actually short-circuit when the data reference is stable.
react-apexcharts pulls in apexcharts (~500KB minified) and was imported synchronously by every dashboard chart component. Switch all five components to React.lazy so the chunk is fetched on demand: - total-balance-long-chart - latest-balance-bar-chart (Simple + Stacked variants) - balance-by-type-area-chart (Plus + Minus + Total variants) - financial-changes-chart (Total + Plus + Minus variants) - total-balance-short-chart (Plus + Minus + Total variants) Each Chart usage is wrapped in a Suspense boundary with a fallback div that matches the chart's height (250/300/400 px) so the layout does not jump while the chunk is in flight. The financial overview screen also fires a non-blocking prefetch for react-apexcharts in a mount-time useEffect, so the chunk is warmed up in parallel with the first data fetch and the chart paint is not stalled on a separate network round-trip.
The prefetch fires off a dynamic import without awaiting the result. An unhandled rejection (e.g. the user navigates away before the chunk arrives) would surface as an uncaught promise warning. Attach a no-op catch so the prefetch stays best-effort.
The inline `?? []` fallbacks created a fresh array reference on every render, which invalidated the chart's internal useMemo caches every time. Wrap the fallback in useMemo so the array identity stays stable while latestBalance is unchanged, letting the chart's downstream memoization actually take effect.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
useDashboardand the derived inputs ofSimpleBarChartso existinguseMemos actually short-circuit instead of invalidating every render.react-apexcharts(~500 KB) behindReact.lazyin all five dashboard chart components, with layout-stable Suspense fallbacks. Prefetch the chunk on overview mount so the first chart paint does not wait on a separate round-trip.Pairs with the backend optimization in DFXswiss/api#3725 (financial log query latency 5-15s -> <500ms).
Changes
Fix A - skip no-op state updates (
src/screens/dashboard-financial-overview.screen.tsx)sameLatestBalance(a, b)andsameLogEntries(a, b)exported alongside the screen for testability.Fix C - stabilize identities
src/hooks/dashboard.hook.ts: move the five async functions (getFinancialLog,getFinancialChanges,getLatestBalance,getLatestChanges,getRefRecipients) inside the existinguseMemo([call])body so consumers actually get stable references across renders.src/components/dashboard/latest-balance-bar-chart.tsx: collapsecategories,valuesandcolors(previously recomputed on every render and used asuseMemodeps) into a singleuseMemo([data]).Fix B - lazy-load ApexCharts
src/components/dashboard/total-balance-long-chart.tsx,latest-balance-bar-chart.tsx,balance-by-type-area-chart.tsx,financial-changes-chart.tsx,total-balance-short-chart.tsx: replaceimport Chart from 'react-apexcharts'withlazy(() => import('react-apexcharts'))and wrap every<Chart />in a<Suspense fallback={<div style={{ height: N }} />}>matching the chart height (250 / 300 / 400 px) so the layout does not jump.dashboard-financial-overview.screen.tsx: fire a best-effortimport('react-apexcharts').catch(...)on mount so the chunk is warmed up in parallel with the first data fetch.Tests
src/__tests__/dashboard-financial-overview.helpers.test.ts: 12 cases covering reference, undefined, empty, length, last-timestamp, and append-tail edge cases for the two new equality helpers.Verification
Build (npm run build:dev):
apexchartschunk2019.*.chunk.js)lazy()- chunk fetched only when a<Suspense>boundary actually rendersTotal JS bytes across all chunks change by < 0.1 % (587 KB vs 587 KB for the chart vendor chunk, same content). The win is on the critical path, not raw bytes: screens that do not render a chart no longer pull in the apex bundle, and the overview prefetches it in parallel with
getLatestBalance/getFinancialLoginstead of blocking on a serial import.Local CI gates (all green):
npm run lintnpm run test-> 295 tests, 27 suites, all passing (12 new)npm run build:devTest plan
/dashboard/financial/overview, watch the network tab: the2019.*.chunk.js(apex) request fires shortly after first paint, layout does not jump when the chart resolves./dashboard/financial/historyafter the overview - the apex chunk should already be in the browser cache (no extra request).TotalBalanceLongChart/BalanceBarChartover a 5-minute window with no backend changes. Expectation: zero renders triggered by the polling refresh.