All Docs
FeaturesMaking Tax DigitalUpdated February 25, 2026

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 ledger
  • BulkRecategoriseBar — 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

ScenarioBeforeAfter
Typing in a date fieldNew API query on every keystrokeOne API query after 300 ms pause
Toggling a filter checkboxFull component tree re-rendersOnly affected sub-components re-render
Scrolling a large transaction listRe-renders on any state changeTable unaffected by unrelated state changes
Checking "Select all"Recalculates all derived valuesOnly 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.