Under the Hood: How We Made the Transaction Ledger Much Faster
Under the Hood: How We Made the Transaction Ledger Much Faster
Version: 1.0.51 Area: Transactions Dashboard
If you manage a large portfolio or frequently filter your transaction list, you may have noticed the transaction ledger feeling a little sluggish — especially when typing into search or date fields. In v1.0.51 we have addressed the root causes of that slowness.
What Was Happening
The transaction ledger (transaction-ledger.tsx) had grown into a single, 24 KB client component responsible for everything: the summary bar at the top, the filter panel, bulk-action controls, and the full transaction table.
Because React re-renders a component and all of its children whenever state changes, every single filter interaction — one keypress, one checkbox toggle — caused the entire page to re-render from scratch. The summary bar, the table rows, the filter controls: all of it, every time.
Worse, because there was no debouncing on date inputs, each keystroke also dispatched a fresh network request to the API. Typing a four-digit year into a date field could trigger four separate queries before you finished.
What We Fixed
1. Debounced Filter Inputs
Date filter inputs now wait 300 milliseconds after the user stops typing before sending a query to the API. This means typing 2024 produces one request instead of four, and the UI remains responsive while you type.
2. Memoized Sub-components
Three major sections of the ledger have been extracted into their own components and wrapped with React.memo:
SummaryBar— the totals row at the top of the ledgerBulkRecategoriseBar— the action bar that appears when rows are selected- Filter panel — the date, category, and search controls
With React.memo, React skips re-rendering these components unless their own props have actually changed. Changing the search term no longer causes the summary bar to re-render, and vice versa.
3. useMemo for Derived Values
Two computed values — hasActiveFilters (used to show/hide the "clear filters" button) and allSelected (used to control the select-all checkbox) — were being recalculated on every render. Both are now wrapped in useMemo so they are only recalculated when their dependencies change.
What This Means for You
| Scenario | Before | After |
|---|---|---|
| Typing in a date field | New API query on every keystroke | One API query after 300 ms pause |
| Toggling a filter checkbox | Full component tree re-renders | Only affected sub-components re-render |
| Scrolling a large transaction list | Re-renders on any state change | Table unaffected by unrelated state changes |
| Checking "Select all" | Recalculates all derived values | Only allSelected recalculated via useMemo |
What's Coming Next
The SummaryBar currently fires its own independent API query separate from the main transaction list query. A future release will either consolidate these into a single query or load the summary bar lazily, further reducing network overhead on initial page load.
These changes are live in v1.0.51 and require no action from users.