From 34c8a73ee523547e315344e3ffd0048be4fb7b0d Mon Sep 17 00:00:00 2001
From: Jed Fox <git@jedfox.com>
Date: Tue, 16 May 2023 14:41:36 -0400
Subject: [PATCH] Remove `@reactions/component` dependency (#1036)

We can use hooks now!
---
 packages/desktop-client/package.json          |   1 -
 .../desktop-client/src/components/Modals.js   |  39 +---
 .../budget/rollover/BudgetSummary.js          | 177 +++++++++---------
 .../src/components/manager/Modals.js          |  31 +--
 .../src/components/modals/LoadBackup.js       |  19 +-
 .../src/components/payees/index.js            |  53 +++---
 upcoming-release-notes/1036.md                |   6 +
 yarn.lock                                     |  10 -
 8 files changed, 152 insertions(+), 184 deletions(-)
 create mode 100644 upcoming-release-notes/1036.md

diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json
index b8a22ae1d..dfe5f5bf5 100644
--- a/packages/desktop-client/package.json
+++ b/packages/desktop-client/package.json
@@ -14,7 +14,6 @@
     "@react-aria/utils": "^3.13.3",
     "@react-stately/collections": "^3.4.3",
     "@react-stately/list": "^3.5.3",
-    "@reactions/component": "^2.0.2",
     "@svgr/cli": "^6.5.1",
     "@testing-library/react": "14.0.0",
     "@testing-library/user-event": "14.4.3",
diff --git a/packages/desktop-client/src/components/Modals.js b/packages/desktop-client/src/components/Modals.js
index 361de99e0..46df9f5b2 100644
--- a/packages/desktop-client/src/components/Modals.js
+++ b/packages/desktop-client/src/components/Modals.js
@@ -2,12 +2,11 @@ import React from 'react';
 import { connect } from 'react-redux';
 import { Route, Switch } from 'react-router-dom';
 
-import Component from '@reactions/component';
 import { createLocation } from 'history';
 import { bindActionCreators } from 'redux';
 
 import * as actions from 'loot-core/src/client/actions';
-import { send, listen, unlisten } from 'loot-core/src/platform/client/fetch';
+import { send } from 'loot-core/src/platform/client/fetch';
 
 import useFeatureFlag from '../hooks/useFeatureFlag';
 import useSyncServerStatus from '../hooks/useSyncServerStatus';
@@ -132,34 +131,14 @@ function Modals({
 
         <Route
           path="/load-backup"
-          render={() => {
-            return (
-              <Component
-                initialState={{ backups: [] }}
-                didMount={async ({ setState }) => {
-                  setState({
-                    backups: await send('backups-get', { id: budgetId }),
-                  });
-
-                  listen('backups-updated', backups => {
-                    setState({ backups });
-                  });
-                }}
-                willUnmount={() => {
-                  unlisten('backups-updated');
-                }}
-              >
-                {({ state }) => (
-                  <LoadBackup
-                    budgetId={budgetId}
-                    modalProps={modalProps}
-                    actions={actions}
-                    backups={state.backups}
-                  />
-                )}
-              </Component>
-            );
-          }}
+          render={() => (
+            <LoadBackup
+              watchUpdates
+              budgetId={budgetId}
+              modalProps={modalProps}
+              actions={actions}
+            />
+          )}
         />
 
         <Route
diff --git a/packages/desktop-client/src/components/budget/rollover/BudgetSummary.js b/packages/desktop-client/src/components/budget/rollover/BudgetSummary.js
index 5edeb6f1f..d9f919fe4 100644
--- a/packages/desktop-client/src/components/budget/rollover/BudgetSummary.js
+++ b/packages/desktop-client/src/components/budget/rollover/BudgetSummary.js
@@ -1,6 +1,5 @@
 import React, { useState } from 'react';
 
-import Component from '@reactions/component';
 import { css } from 'glamor';
 
 import { rolloverBudget } from 'loot-core/src/client/queries';
@@ -135,6 +134,8 @@ function TotalsList({ prevMonthName, collapsed }) {
 }
 
 function ToBudget({ month, prevMonthName, collapsed, onBudgetAction }) {
+  let [menuOpen, setMenuOpen] = useState(null);
+
   return (
     <SheetValue binding={rolloverBudget.toBudget} initialValue={0}>
       {node => {
@@ -145,97 +146,93 @@ function ToBudget({ month, prevMonthName, collapsed, onBudgetAction }) {
         return (
           <View style={{ alignItems: 'center' }}>
             <Block>{isNegative ? 'Overbudgeted:' : 'To Budget:'}</Block>
-            <Component initialState={{ menuOpen: null }}>
-              {({ state, setState }) => (
-                <View>
-                  <HoverTarget
-                    disabled={!collapsed || state.menuOpen}
-                    renderContent={() => (
-                      <Tooltip position="bottom-center">
-                        <TotalsList
-                          collapsed={true}
-                          prevMonthName={prevMonthName}
-                        />
-                      </Tooltip>
-                    )}
-                  >
-                    <Block
-                      onClick={() => setState({ menuOpen: 'actions' })}
-                      data-cellname={node.name}
-                      {...css([
-                        styles.veryLargeText,
-                        {
-                          fontWeight: 400,
-                          userSelect: 'none',
-                          cursor: 'pointer',
-                          color: isNegative ? colors.r4 : colors.p5,
-                          marginBottom: -1,
-                          borderBottom: '1px solid transparent',
-                          ':hover': {
-                            borderColor: isNegative ? colors.r4 : colors.p5,
-                          },
-                        },
-                      ])}
-                    >
-                      {format(num, 'financial')}
-                    </Block>
-                  </HoverTarget>
-                  {state.menuOpen === 'actions' && (
-                    <Tooltip
-                      position="bottom-center"
-                      width={200}
-                      style={{ padding: 0 }}
-                      onClose={() => setState({ menuOpen: null })}
-                    >
-                      <Menu
-                        onMenuSelect={type => {
-                          if (type === 'reset-buffer') {
-                            onBudgetAction(month, 'reset-hold');
-                            setState({ menuOpen: null });
-                          } else {
-                            setState({ menuOpen: type });
-                          }
-                        }}
-                        items={[
-                          {
-                            name: 'transfer',
-                            text: 'Move to a category',
-                          },
-                          {
-                            name: 'buffer',
-                            text: 'Hold for next month',
-                          },
-                          {
-                            name: 'reset-buffer',
-                            text: 'Reset next month’s buffer',
-                          },
-                        ]}
-                      />
-                    </Tooltip>
-                  )}
-                  {state.menuOpen === 'buffer' && (
-                    <HoldTooltip
-                      onClose={() => setState({ menuOpen: null })}
-                      onSubmit={amount => {
-                        onBudgetAction(month, 'hold', { amount });
-                      }}
-                    />
-                  )}
-                  {state.menuOpen === 'transfer' && (
-                    <TransferTooltip
-                      initialAmount={availableValue}
-                      onClose={() => setState({ menuOpen: null })}
-                      onSubmit={(amount, category) => {
-                        onBudgetAction(month, 'transfer-available', {
-                          amount,
-                          category,
-                        });
-                      }}
+            <View>
+              <HoverTarget
+                disabled={!collapsed || menuOpen}
+                renderContent={() => (
+                  <Tooltip position="bottom-center">
+                    <TotalsList
+                      collapsed={true}
+                      prevMonthName={prevMonthName}
                     />
-                  )}
-                </View>
+                  </Tooltip>
+                )}
+              >
+                <Block
+                  onClick={() => setMenuOpen('actions')}
+                  data-cellname={node.name}
+                  {...css([
+                    styles.veryLargeText,
+                    {
+                      fontWeight: 400,
+                      userSelect: 'none',
+                      cursor: 'pointer',
+                      color: isNegative ? colors.r4 : colors.p5,
+                      marginBottom: -1,
+                      borderBottom: '1px solid transparent',
+                      ':hover': {
+                        borderColor: isNegative ? colors.r4 : colors.p5,
+                      },
+                    },
+                  ])}
+                >
+                  {format(num, 'financial')}
+                </Block>
+              </HoverTarget>
+              {menuOpen === 'actions' && (
+                <Tooltip
+                  position="bottom-center"
+                  width={200}
+                  style={{ padding: 0 }}
+                  onClose={() => setMenuOpen(null)}
+                >
+                  <Menu
+                    onMenuSelect={type => {
+                      if (type === 'reset-buffer') {
+                        onBudgetAction(month, 'reset-hold');
+                        setMenuOpen(null);
+                      } else {
+                        setMenuOpen(type);
+                      }
+                    }}
+                    items={[
+                      {
+                        name: 'transfer',
+                        text: 'Move to a category',
+                      },
+                      {
+                        name: 'buffer',
+                        text: 'Hold for next month',
+                      },
+                      {
+                        name: 'reset-buffer',
+                        text: 'Reset next month’s buffer',
+                      },
+                    ]}
+                  />
+                </Tooltip>
+              )}
+              {menuOpen === 'buffer' && (
+                <HoldTooltip
+                  onClose={() => setMenuOpen(null)}
+                  onSubmit={amount => {
+                    onBudgetAction(month, 'hold', { amount });
+                  }}
+                />
               )}
-            </Component>
+              {menuOpen === 'transfer' && (
+                <TransferTooltip
+                  initialAmount={availableValue}
+                  onClose={() => setMenuOpen(null)}
+                  onSubmit={(amount, category) => {
+                    onBudgetAction(month, 'transfer-available', {
+                      amount,
+                      category,
+                    });
+                  }}
+                />
+              )}
+            </View>
           </View>
         );
       }}
diff --git a/packages/desktop-client/src/components/manager/Modals.js b/packages/desktop-client/src/components/manager/Modals.js
index ebd1bce5f..bbc467122 100644
--- a/packages/desktop-client/src/components/manager/Modals.js
+++ b/packages/desktop-client/src/components/manager/Modals.js
@@ -1,11 +1,9 @@
 import React from 'react';
 import { connect } from 'react-redux';
 
-import Component from '@reactions/component';
 import { bindActionCreators } from 'redux';
 
 import * as actions from 'loot-core/src/client/actions';
-import { send } from 'loot-core/src/platform/client/fetch';
 
 import { View } from '../common';
 import CreateEncryptionKey from '../modals/CreateEncryptionKey';
@@ -70,28 +68,15 @@ function Modals({
         );
       case 'load-backup': {
         return (
-          <Component
-            key={name}
-            initialState={{ backups: [] }}
-            didMount={async ({ setState }) => {
-              setState({
-                backups: await send('backups-get', { id: options.budgetId }),
-              });
+          <LoadBackup
+            budgetId={options.budgetId}
+            modalProps={{
+              ...modalProps,
+              onClose: actions.popModal,
             }}
-          >
-            {({ state }) => (
-              <LoadBackup
-                budgetId={options.budgetId}
-                modalProps={{
-                  ...modalProps,
-                  onClose: actions.popModal,
-                }}
-                backupDisabled={true}
-                actions={actions}
-                backups={state.backups}
-              />
-            )}
-          </Component>
+            backupDisabled={true}
+            actions={actions}
+          />
         );
       }
       case 'create-encryption-key':
diff --git a/packages/desktop-client/src/components/modals/LoadBackup.js b/packages/desktop-client/src/components/modals/LoadBackup.js
index 77e5cc80f..1582822e1 100644
--- a/packages/desktop-client/src/components/modals/LoadBackup.js
+++ b/packages/desktop-client/src/components/modals/LoadBackup.js
@@ -1,4 +1,6 @@
-import React, { Component } from 'react';
+import React, { Component, useState, useEffect } from 'react';
+
+import { send, listen, unlisten } from 'loot-core/src/platform/client/fetch';
 
 import { colors } from '../../style';
 import { View, Text, Block, Modal, Button } from '../common';
@@ -43,11 +45,24 @@ class BackupTable extends Component {
 
 function LoadBackup({
   budgetId,
-  backups,
+  watchUpdates,
   backupDisabled,
   actions,
   modalProps,
 }) {
+  let [backups, setBackups] = useState([]);
+
+  useEffect(() => {
+    send('backups-get', { id: budgetId }).then(setBackups);
+  }, [budgetId]);
+
+  useEffect(() => {
+    if (watchUpdates) {
+      listen('backups-updated', setBackups);
+      return () => unlisten('backups-updated', setBackups);
+    }
+  }, [watchUpdates]);
+
   const latestBackup = backups.find(backup => backup.isLatest);
   const previousBackups = backups.filter(backup => !backup.isLatest);
 
diff --git a/packages/desktop-client/src/components/payees/index.js b/packages/desktop-client/src/components/payees/index.js
index ce152cf7b..65a9c5e5e 100644
--- a/packages/desktop-client/src/components/payees/index.js
+++ b/packages/desktop-client/src/components/payees/index.js
@@ -10,7 +10,6 @@ import React, {
   useImperativeHandle,
 } from 'react';
 
-import Component from '@reactions/component';
 import memoizeOne from 'memoize-one';
 
 import { groupById } from 'loot-core/src/shared/util';
@@ -475,6 +474,8 @@ export const ManagePayees = forwardRef(
 
     let payeesById = getPayeesById(payees);
 
+    let [menuOpen, setMenuOpen] = useState(false);
+
     return (
       <View style={{ height: '100%' }}>
         <View
@@ -484,34 +485,30 @@ export const ManagePayees = forwardRef(
             padding: '0 10px 5px',
           }}
         >
-          <Component initialState={{ menuOpen: false }}>
-            {({ state, setState }) => (
-              <View>
-                <Button
-                  bare
-                  style={{ marginRight: 10 }}
-                  disabled={buttonsDisabled}
-                  onClick={() => setState({ menuOpen: true })}
-                >
-                  {buttonsDisabled
-                    ? 'No payees selected'
-                    : selected.items.size +
-                      ' ' +
-                      plural(selected.items.size, 'payee', 'payees')}
-                  <ExpandArrow width={8} height={8} style={{ marginLeft: 5 }} />
-                </Button>
-                {state.menuOpen && (
-                  <PayeeMenu
-                    payeesById={payeesById}
-                    selectedPayees={selected.items}
-                    onClose={() => setState({ menuOpen: false })}
-                    onDelete={onDelete}
-                    onMerge={onMerge}
-                  />
-                )}
-              </View>
+          <View>
+            <Button
+              bare
+              style={{ marginRight: 10 }}
+              disabled={buttonsDisabled}
+              onClick={() => setMenuOpen(true)}
+            >
+              {buttonsDisabled
+                ? 'No payees selected'
+                : selected.items.size +
+                  ' ' +
+                  plural(selected.items.size, 'payee', 'payees')}
+              <ExpandArrow width={8} height={8} style={{ marginLeft: 5 }} />
+            </Button>
+            {menuOpen && (
+              <PayeeMenu
+                payeesById={payeesById}
+                selectedPayees={selected.items}
+                onClose={() => setMenuOpen(false)}
+                onDelete={onDelete}
+                onMerge={onMerge}
+              />
             )}
-          </Component>
+          </View>
           <View>
             <Button
               bare
diff --git a/upcoming-release-notes/1036.md b/upcoming-release-notes/1036.md
new file mode 100644
index 000000000..1c6ee006d
--- /dev/null
+++ b/upcoming-release-notes/1036.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [j-f1]
+---
+
+Remove dependency on `@reactions/component`
diff --git a/yarn.lock b/yarn.lock
index 83fcfba0d..42323add1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -63,7 +63,6 @@ __metadata:
     "@react-aria/utils": ^3.13.3
     "@react-stately/collections": ^3.4.3
     "@react-stately/list": ^3.5.3
-    "@reactions/component": ^2.0.2
     "@svgr/cli": ^6.5.1
     "@testing-library/react": 14.0.0
     "@testing-library/user-event": 14.4.3
@@ -2950,15 +2949,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@reactions/component@npm:^2.0.2":
-  version: 2.0.2
-  resolution: "@reactions/component@npm:2.0.2"
-  peerDependencies:
-    react: 15.x || 16.x
-  checksum: 89d806e2b15974f213806e69a23b1ad2f6675cab93daf1fa56a7df2c74403be4adb5da9a5c64adb89e55e98d767fbe100c289ed615b15645894bef8c03ef87c6
-  languageName: node
-  linkType: hard
-
 "@remix-run/router@npm:1.0.1":
   version: 1.0.1
   resolution: "@remix-run/router@npm:1.0.1"
-- 
GitLab