diff --git a/packages/desktop-client/src/components/budget/BudgetTable.jsx b/packages/desktop-client/src/components/budget/BudgetTable.jsx
index db7826460c0289c4c7be0342f32f38b135492f68..cc61c3d8b5ee522f43ecfe0f785f868c7492f8df 100644
--- a/packages/desktop-client/src/components/budget/BudgetTable.jsx
+++ b/packages/desktop-client/src/components/budget/BudgetTable.jsx
@@ -1,9 +1,9 @@
-import React, { createRef, Component } from 'react';
-import { connect } from 'react-redux';
+import React, { useRef, useState } from 'react';
 
-import { savePrefs } from 'loot-core/src/client/actions';
 import * as monthUtils from 'loot-core/src/shared/months';
 
+import { useCategories } from '../../hooks/useCategories';
+import { useLocalPref } from '../../hooks/useLocalPref';
 import { theme, styles } from '../../style';
 import { View } from '../common/View';
 import { IntersectionBoundary } from '../tooltips';
@@ -14,28 +14,41 @@ import { BudgetTotals } from './BudgetTotals';
 import { MonthsProvider } from './MonthsContext';
 import { findSortDown, findSortUp, getScrollbarWidth } from './util';
 
-class BudgetTableInner extends Component {
-  constructor(props) {
-    super(props);
-    this.budgetCategoriesRef = createRef();
-
-    this.state = {
-      editing: null,
-      draggingState: null,
-    };
-  }
-
-  onEditMonth = (id, monthIndex) => {
-    this.setState({ editing: id ? { id, cell: monthIndex } : null });
+export function BudgetTable(props) {
+  const {
+    type,
+    prewarmStartMonth,
+    startMonth,
+    numMonths,
+    monthBounds,
+    dataComponents,
+    onSaveCategory,
+    onDeleteCategory,
+    onSaveGroup,
+    onDeleteGroup,
+    onReorderCategory,
+    onReorderGroup,
+    onShowActivity,
+    onBudgetAction,
+  } = props;
+
+  const budgetCategoriesRef = useRef();
+  const { grouped: categoryGroups } = useCategories();
+  const [collapsed = [], setCollapsedPref] = useLocalPref('budget.collapsed');
+  const [showHiddenCategories, setShowHiddenCategoriesPef] = useLocalPref(
+    'budget.showHiddenCategories',
+  );
+  const [editing, setEditing] = useState(null);
+
+  const onEditMonth = (id, monthIndex) => {
+    setEditing(id ? { id, cell: monthIndex } : null);
   };
 
-  onEditName = id => {
-    this.setState({ editing: id ? { id, cell: 'name' } : null });
+  const onEditName = id => {
+    setEditing(id ? { id, cell: 'name' } : null);
   };
 
-  onReorderCategory = (id, dropPos, targetId) => {
-    const { categoryGroups } = this.props;
-
+  const _onReorderCategory = (id, dropPos, targetId) => {
     const isGroup = !!categoryGroups.find(g => g.id === targetId);
 
     if (isGroup) {
@@ -48,7 +61,7 @@ class BudgetTableInner extends Component {
 
       if (group) {
         const { categories } = group;
-        this.props.onReorderCategory({
+        onReorderCategory({
           id,
           groupId: group.id,
           targetId:
@@ -67,7 +80,7 @@ class BudgetTableInner extends Component {
         }
       }
 
-      this.props.onReorderCategory({
+      onReorderCategory({
         id,
         groupId: targetGroup.id,
         ...findSortDown(targetGroup.categories, dropPos, targetId),
@@ -75,19 +88,14 @@ class BudgetTableInner extends Component {
     }
   };
 
-  onReorderGroup = (id, dropPos, targetId) => {
-    const { categoryGroups } = this.props;
-
-    this.props.onReorderGroup({
+  const _onReorderGroup = (id, dropPos, targetId) => {
+    onReorderGroup({
       id,
       ...findSortDown(categoryGroups, dropPos, targetId),
     });
   };
 
-  moveVertically = dir => {
-    const { editing } = this.state;
-    const { type, categoryGroups, collapsed } = this.props;
-
+  const moveVertically = dir => {
     const flattened = categoryGroups.reduce((all, group) => {
       if (collapsed.includes(group.id)) {
         return all.concat({ id: group.id, isGroup: true });
@@ -106,7 +114,7 @@ class BudgetTableInner extends Component {
           nextIdx += dir;
           continue;
         } else if (type === 'report' || !next.is_income) {
-          this.onEditMonth(next.id, editing.cell);
+          onEditMonth(next.id, editing.cell);
           return;
         } else {
           break;
@@ -115,187 +123,136 @@ class BudgetTableInner extends Component {
     }
   };
 
-  onKeyDown = e => {
-    if (!this.state.editing) {
+  const onKeyDown = e => {
+    if (!editing) {
       return null;
     }
 
     if (e.key === 'Enter' || e.key === 'Tab') {
       e.preventDefault();
-      this.moveVertically(e.shiftKey ? -1 : 1);
+      moveVertically(e.shiftKey ? -1 : 1);
     }
   };
 
-  onShowActivity = (catId, monthIndex) => {
-    this.props.onShowActivity(catId, this.resolveMonth(monthIndex));
+  const resolveMonth = monthIndex => {
+    return monthUtils.addMonths(startMonth, monthIndex);
   };
 
-  onBudgetAction = (monthIndex, type, args) => {
-    this.props.onBudgetAction(this.resolveMonth(monthIndex), type, args);
+  const _onShowActivity = (catId, monthIndex) => {
+    onShowActivity(catId, resolveMonth(monthIndex));
   };
 
-  resolveMonth = monthIndex => {
-    return monthUtils.addMonths(this.props.startMonth, monthIndex);
+  const _onBudgetAction = (monthIndex, type, args) => {
+    onBudgetAction(resolveMonth(monthIndex), type, args);
   };
 
-  // This is called via ref.
-  clearEditing() {
-    this.setState({ editing: null });
-  }
+  const onCollapse = collapsedIds => {
+    setCollapsedPref(collapsedIds);
+  };
 
-  toggleHiddenCategories = () => {
-    this.props.onToggleHiddenCategories();
+  const onToggleHiddenCategories = () => {
+    setShowHiddenCategoriesPef(!showHiddenCategories);
   };
 
-  expandAllCategories = () => {
-    this.props.onCollapse([]);
+  const toggleHiddenCategories = () => {
+    onToggleHiddenCategories();
   };
 
-  collapseAllCategories = () => {
-    const { onCollapse, categoryGroups } = this.props;
-    onCollapse(categoryGroups.map(g => g.id));
+  const expandAllCategories = () => {
+    onCollapse([]);
   };
 
-  render() {
-    const {
-      type,
-      categoryGroups,
-      prewarmStartMonth,
-      startMonth,
-      numMonths,
-      monthBounds,
-      dataComponents,
-      onSaveCategory,
-      onSaveGroup,
-      onDeleteCategory,
-      onDeleteGroup,
-    } = this.props;
-    const { editing, draggingState } = this.state;
+  const collapseAllCategories = () => {
+    onCollapse(categoryGroups.map(g => g.id));
+  };
 
-    return (
+  return (
+    <View
+      data-testid="budget-table"
+      style={{
+        flex: 1,
+        ...(styles.lightScrollbar && {
+          '& ::-webkit-scrollbar': {
+            backgroundColor: 'transparent',
+          },
+          '& ::-webkit-scrollbar-thumb:vertical': {
+            backgroundColor: theme.tableHeaderBackground,
+          },
+        }),
+      }}
+    >
       <View
-        data-testid="budget-table"
         style={{
-          flex: 1,
-          ...(styles.lightScrollbar && {
-            '& ::-webkit-scrollbar': {
-              backgroundColor: 'transparent',
-            },
-            '& ::-webkit-scrollbar-thumb:vertical': {
-              backgroundColor: theme.tableHeaderBackground,
-            },
-          }),
+          flexDirection: 'row',
+          overflow: 'hidden',
+          flexShrink: 0,
+          // This is necessary to align with the table because the
+          // table has this padding to allow the shadow to show
+          paddingLeft: 5,
+          paddingRight: 5 + getScrollbarWidth(),
         }}
       >
-        <View
-          style={{
-            flexDirection: 'row',
-            overflow: 'hidden',
-            flexShrink: 0,
-            // This is necessary to align with the table because the
-            // table has this padding to allow the shadow to show
-            paddingLeft: 5,
-            paddingRight: 5 + getScrollbarWidth(),
-          }}
-        >
-          <View style={{ width: 200 }} />
-          <MonthsProvider
-            startMonth={prewarmStartMonth}
-            numMonths={numMonths}
-            monthBounds={monthBounds}
-            type={type}
-          >
-            <BudgetSummaries
-              SummaryComponent={dataComponents.SummaryComponent}
-            />
-          </MonthsProvider>
-        </View>
-
+        <View style={{ width: 200 }} />
         <MonthsProvider
-          startMonth={startMonth}
+          startMonth={prewarmStartMonth}
           numMonths={numMonths}
           monthBounds={monthBounds}
           type={type}
         >
-          <BudgetTotals
-            MonthComponent={dataComponents.BudgetTotalsComponent}
-            toggleHiddenCategories={this.toggleHiddenCategories}
-            expandAllCategories={this.expandAllCategories}
-            collapseAllCategories={this.collapseAllCategories}
-          />
-          <IntersectionBoundary.Provider value={this.budgetCategoriesRef}>
+          <BudgetSummaries SummaryComponent={dataComponents.SummaryComponent} />
+        </MonthsProvider>
+      </View>
+
+      <MonthsProvider
+        startMonth={startMonth}
+        numMonths={numMonths}
+        monthBounds={monthBounds}
+        type={type}
+      >
+        <BudgetTotals
+          MonthComponent={dataComponents.BudgetTotalsComponent}
+          toggleHiddenCategories={toggleHiddenCategories}
+          expandAllCategories={expandAllCategories}
+          collapseAllCategories={collapseAllCategories}
+        />
+        <IntersectionBoundary.Provider value={budgetCategoriesRef}>
+          <View
+            style={{
+              overflowY: 'scroll',
+              overflowAnchor: 'none',
+              flex: 1,
+              paddingLeft: 5,
+              paddingRight: 5,
+            }}
+            innerRef={budgetCategoriesRef}
+          >
             <View
               style={{
-                overflowY: 'scroll',
-                overflowAnchor: 'none',
-                flex: 1,
-                paddingLeft: 5,
-                paddingRight: 5,
+                flexShrink: 0,
               }}
-              innerRef={this.budgetCategoriesRef}
+              onKeyDown={onKeyDown}
             >
-              <View
-                style={{
-                  opacity: draggingState ? 0.5 : 1,
-                  flexShrink: 0,
-                }}
-                onKeyDown={this.onKeyDown}
-                innerRef={el => (this.budgetDataNode = el)}
-              >
-                <BudgetCategories
-                  categoryGroups={categoryGroups}
-                  editingCell={editing}
-                  dataComponents={dataComponents}
-                  onEditMonth={this.onEditMonth}
-                  onEditName={this.onEditName}
-                  onSaveCategory={onSaveCategory}
-                  onSaveGroup={onSaveGroup}
-                  onDeleteCategory={onDeleteCategory}
-                  onDeleteGroup={onDeleteGroup}
-                  onReorderCategory={this.onReorderCategory}
-                  onReorderGroup={this.onReorderGroup}
-                  onBudgetAction={this.onBudgetAction}
-                  onShowActivity={this.onShowActivity}
-                />
-              </View>
+              <BudgetCategories
+                categoryGroups={categoryGroups}
+                editingCell={editing}
+                dataComponents={dataComponents}
+                onEditMonth={onEditMonth}
+                onEditName={onEditName}
+                onSaveCategory={onSaveCategory}
+                onSaveGroup={onSaveGroup}
+                onDeleteCategory={onDeleteCategory}
+                onDeleteGroup={onDeleteGroup}
+                onReorderCategory={_onReorderCategory}
+                onReorderGroup={_onReorderGroup}
+                onBudgetAction={_onBudgetAction}
+                onShowActivity={_onShowActivity}
+              />
             </View>
-          </IntersectionBoundary.Provider>
-        </MonthsProvider>
-      </View>
-    );
-  }
+          </View>
+        </IntersectionBoundary.Provider>
+      </MonthsProvider>
+    </View>
+  );
 }
 
-const mapStateToProps = state => {
-  const { grouped: categoryGroups } = state.queries.categories;
-  const collapsed = state.prefs.local?.['budget.collapsed'] || [];
-  return {
-    categoryGroups,
-    collapsed,
-  };
-};
-
-const mapDispatchToProps = dispatch => {
-  const onCollapse = collapsedIds => {
-    dispatch(savePrefs({ 'budget.collapsed': collapsedIds }));
-  };
-
-  const onToggleHiddenCategories = () =>
-    dispatch((innerDispatch, getState) => {
-      const { prefs } = getState();
-      const showHiddenCategories = prefs.local['budget.showHiddenCategories'];
-      innerDispatch(
-        savePrefs({
-          'budget.showHiddenCategories': !showHiddenCategories,
-        }),
-      );
-    });
-  return {
-    onCollapse,
-    onToggleHiddenCategories,
-  };
-};
-
-export const BudgetTable = connect(mapStateToProps, mapDispatchToProps, null, {
-  forwardRef: true,
-})(BudgetTableInner);
+BudgetTable.displayName = 'BudgetTable';
diff --git a/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx b/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx
index 1422a021c973f5755045c6181d0a25636b1aaad3..60e97052b8e9c0cf702f35e890cbf3e02c1e3877 100644
--- a/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx
+++ b/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx
@@ -1,8 +1,7 @@
 // @ts-strict-ignore
-import React, { forwardRef, useEffect, type ComponentProps } from 'react';
+import React, { useEffect, type ComponentProps } from 'react';
 import AutoSizer from 'react-virtualized-auto-sizer';
 
-import { useActions } from '../../hooks/useActions';
 import { View } from '../common/View';
 
 import { useBudgetMonthCount } from './BudgetMonthCountContext';
@@ -30,90 +29,72 @@ function getNumPossibleMonths(width: number) {
 type DynamicBudgetTableInnerProps = {
   width: number;
   height: number;
-} & ComponentProps<typeof BudgetTable>;
+} & DynamicBudgetTableProps;
 
-const DynamicBudgetTableInner = forwardRef<
-  typeof BudgetTable,
-  DynamicBudgetTableInnerProps
->(
-  (
-    {
-      width,
-      height,
-      prewarmStartMonth,
-      startMonth,
-      maxMonths = 3,
-      monthBounds,
-      onMonthSelect: onMonthSelect_,
-      onPreload,
-      ...props
-    },
-    ref,
-  ) => {
-    const { setDisplayMax } = useBudgetMonthCount();
-    const actions = useActions();
+const DynamicBudgetTableInner = ({
+  width,
+  height,
+  prewarmStartMonth,
+  startMonth,
+  maxMonths = 3,
+  monthBounds,
+  onMonthSelect,
+  ...props
+}: DynamicBudgetTableInnerProps) => {
+  const { setDisplayMax } = useBudgetMonthCount();
 
-    const numPossible = getNumPossibleMonths(width);
-    const numMonths = Math.min(numPossible, maxMonths);
-    const maxWidth = 200 + 500 * numMonths;
+  const numPossible = getNumPossibleMonths(width);
+  const numMonths = Math.min(numPossible, maxMonths);
+  const maxWidth = 200 + 500 * numMonths;
 
-    useEffect(() => {
-      setDisplayMax(numPossible);
-    }, [numPossible]);
+  useEffect(() => {
+    setDisplayMax(numPossible);
+  }, [numPossible]);
 
-    function onMonthSelect(month) {
-      onMonthSelect_(month, numMonths);
-    }
+  function _onMonthSelect(month) {
+    onMonthSelect(month, numMonths);
+  }
 
-    return (
-      <View
-        style={{
-          width,
-          height,
-          alignItems: 'center',
-          opacity: width <= 0 || height <= 0 ? 0 : 1,
-        }}
-      >
-        <View style={{ width: '100%', maxWidth }}>
-          <BudgetPageHeader
-            startMonth={prewarmStartMonth}
-            numMonths={numMonths}
-            monthBounds={monthBounds}
-            onMonthSelect={onMonthSelect}
-          />
-          <BudgetTable
-            ref={ref}
-            prewarmStartMonth={prewarmStartMonth}
-            startMonth={startMonth}
-            numMonths={numMonths}
-            monthBounds={monthBounds}
-            {...actions}
-            {...props}
-          />
-        </View>
+  return (
+    <View
+      style={{
+        width,
+        height,
+        alignItems: 'center',
+        opacity: width <= 0 || height <= 0 ? 0 : 1,
+      }}
+    >
+      <View style={{ width: '100%', maxWidth }}>
+        <BudgetPageHeader
+          startMonth={prewarmStartMonth}
+          numMonths={numMonths}
+          monthBounds={monthBounds}
+          onMonthSelect={_onMonthSelect}
+        />
+        <BudgetTable
+          prewarmStartMonth={prewarmStartMonth}
+          startMonth={startMonth}
+          numMonths={numMonths}
+          monthBounds={monthBounds}
+          {...props}
+        />
       </View>
-    );
-  },
-);
+    </View>
+  );
+};
 
 DynamicBudgetTableInner.displayName = 'DynamicBudgetTableInner';
 
-export const DynamicBudgetTable = forwardRef<
-  typeof BudgetTable,
-  DynamicBudgetTableInnerProps
->((props, ref) => {
+type DynamicBudgetTableProps = ComponentProps<typeof BudgetTable>;
+
+export const DynamicBudgetTable = (props: DynamicBudgetTableProps) => {
   return (
     <AutoSizer>
       {({ width, height }) => (
-        <DynamicBudgetTableInner
-          ref={ref}
-          width={width}
-          height={height}
-          {...props}
-        />
+        <DynamicBudgetTableInner width={width} height={height} {...props} />
       )}
     </AutoSizer>
   );
-});
+};
 
 DynamicBudgetTable.displayName = 'DynamicBudgetTable';
diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx
index ed36d976b254d49e80b1041d25a9a1317ed2b354..2d339b755ad300a649364630721bc8935a19841d 100644
--- a/packages/desktop-client/src/components/budget/index.tsx
+++ b/packages/desktop-client/src/components/budget/index.tsx
@@ -1,12 +1,5 @@
 // @ts-strict-ignore
-import React, {
-  memo,
-  useContext,
-  useMemo,
-  useState,
-  useEffect,
-  useRef,
-} from 'react';
+import React, { memo, useContext, useMemo, useState, useEffect } from 'react';
 import { useDispatch } from 'react-redux';
 
 import {
@@ -69,16 +62,15 @@ type RolloverComponents = {
   IncomeHeaderComponent: typeof rollover.IncomeHeaderMonth;
 };
 
-type BudgetProps = {
+type BudgetInnerProps = {
   accountId?: string;
   reportComponents: ReportComponents;
   rolloverComponents: RolloverComponents;
   titlebar: TitlebarContextValue;
 };
 
-function BudgetInner(props: BudgetProps) {
+function BudgetInner(props: BudgetInnerProps) {
   const currentMonth = monthUtils.currentMonth();
-  const tableRef = useRef(null);
   const spreadsheet = useSpreadsheet();
   const dispatch = useDispatch();
   const navigate = useNavigate();
@@ -138,17 +130,6 @@ function BudgetInner(props: BudgetProps) {
       }),
 
       listen('undo-event', ({ tables }) => {
-        if (tableRef.current) {
-          // g dammit
-          // We need to clear the editing cell, otherwise when
-          // the user navigates away from the page they will
-          // accidentally clear the undo stack if they have pressed
-          // undo, since the cell will save itself on blur (worst case:
-          // undo takes you to another screen and then you can't redo
-          // any of the budget changes)
-          tableRef.current.clearEditing();
-        }
-
         if (tables.includes('categories')) {
           loadCategories();
         }
@@ -377,7 +358,6 @@ function BudgetInner(props: BudgetProps) {
         onToggleSummaryCollapse={onToggleCollapse}
       >
         <DynamicBudgetTable
-          ref={tableRef}
           type={budgetType}
           prewarmStartMonth={startMonth}
           startMonth={startMonth}
@@ -404,7 +384,6 @@ function BudgetInner(props: BudgetProps) {
         onToggleSummaryCollapse={onToggleCollapse}
       >
         <DynamicBudgetTable
-          ref={tableRef}
           type={budgetType}
           prewarmStartMonth={startMonth}
           startMonth={startMonth}
diff --git a/upcoming-release-notes/2459.md b/upcoming-release-notes/2459.md
new file mode 100644
index 0000000000000000000000000000000000000000..e9ae5d9ed6fbea0f5940489f4ebfa78134e538e6
--- /dev/null
+++ b/upcoming-release-notes/2459.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [joel-jeremy]
+---
+
+Convert BudgetTable component to a functional component.