Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Redux Toolkit Migration] accountsSlice #4012

Merged
merged 24 commits into from
Jan 13, 2025
Merged

Conversation

joel-jeremy
Copy link
Contributor

@joel-jeremy joel-jeremy commented Dec 19, 2024

  • actions/account.ts / reducers/account.ts -> accounts/accountsSlice.ts [Redux Toolkit Migration] accountsSlice #4012
  • actions/queries.ts / reducers/queries.ts -> queries/queriesSlice.ts
  • actions/app.ts / reducers/app.ts -> app/appSlice.ts
  • actions/budgets.ts / reducers/budgets.ts -> budgets/budgetsSlice.ts
  • actions/modals.ts / reducers/modals.ts -> modals/modalsSlice.ts
  • actions/notifications.ts / reducers/notifications.ts -> notifications/notificationsSlice.ts
  • actions/prefs.ts / reducers/prefs.ts -> prefs/prefsSlice.ts
  • actions/user.ts / reducers/user.ts -> users/usersSlice.ts

@actual-github-bot actual-github-bot bot changed the title [Redux Toolkit Migration] accountSlice [WIP] [Redux Toolkit Migration] accountSlice Dec 19, 2024
Copy link

netlify bot commented Dec 19, 2024

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit f123633
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/678568bdcca24800087d04ca
😎 Deploy Preview https://deploy-preview-4012.demo.actualbudget.org
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

github-actions bot commented Dec 19, 2024

Bundle Stats — desktop-client

Hey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle.

As this PR is updated, I'll keep you updated on how the bundle size is impacted.

Total

Files count Total bundle size % Changed
18 → 21 5.92 MB → 5.96 MB (+37.54 kB) +0.62%
Changeset
File Δ Size
node_modules/immer/dist/immer.mjs 🆕 +17.74 kB 0 B → 17.74 kB
home/runner/work/actual/actual/packages/loot-core/src/client/accounts/accountsSlice.ts 🆕 +5.12 kB 0 B → 5.12 kB
home/runner/work/actual/actual/packages/loot-core/src/client/redux/index.ts 🆕 +57 B 0 B → 57 B
locale/pt-BR.json 🆕 +19 B 0 B → 19 B
locale/nb-NO.json 🆕 +19 B 0 B → 19 B
locale/cy.json 🆕 +17 B 0 B → 17 B
node_modules/@reduxjs/toolkit/dist/redux-toolkit.modern.mjs 📈 +17.97 kB (+354.33%) 5.07 kB → 23.04 kB
src/i18n.ts 📈 +378 B (+20.31%) 1.82 kB → 2.19 kB
home/runner/work/actual/actual/packages/loot-core/src/client/actions/queries.ts 📈 +1.64 kB (+19.39%) 8.43 kB → 10.07 kB
src/global-events.ts 📈 +158 B (+5.02%) 3.07 kB → 3.23 kB
locale/fr.json 📈 +516 B (+4.00%) 12.6 kB → 13.11 kB
home/runner/work/actual/actual/packages/loot-core/src/client/store/index.ts 📈 +33 B (+3.34%) 988 B → 1021 B
home/runner/work/actual/actual/packages/loot-core/src/client/shared-listeners.ts 📈 +243 B (+2.94%) 8.08 kB → 8.32 kB
src/components/modals/SelectLinkedAccountsModal.jsx 📈 +147 B (+1.82%) 7.87 kB → 8.02 kB
node_modules/redux/dist/redux.mjs 📈 +119 B (+1.39%) 8.33 kB → 8.45 kB
src/index.tsx 📈 +11 B (+1.35%) 812 B → 823 B
home/runner/work/actual/actual/packages/loot-core/src/client/actions/sync.ts 📈 +18 B (+1.21%) 1.45 kB → 1.47 kB
src/components/sidebar/Accounts.tsx 📈 +30 B (+0.64%) 4.61 kB → 4.64 kB
src/components/accounts/AccountSyncCheck.tsx 📈 +22 B (+0.37%) 5.85 kB → 5.87 kB
src/components/modals/PasswordEnableModal.tsx 📈 +14 B (+0.32%) 4.26 kB → 4.27 kB
src/components/admin/UserAccess/UserAccessRow.tsx 📈 +10 B (+0.28%) 3.51 kB → 3.52 kB
src/components/accounts/Account.tsx 📈 +126 B (+0.27%) 45.79 kB → 45.91 kB
src/components/modals/TransferOwnership.tsx 📈 +12 B (+0.19%) 6.32 kB → 6.33 kB
package.json 📈 +4 B (+0.13%) 2.97 kB → 2.97 kB
src/components/modals/EditUser.tsx 📈 +14 B (+0.10%) 13.32 kB → 13.33 kB
src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx 📈 +26 B (+0.08%) 30.05 kB → 30.07 kB
src/components/modals/EditAccess.tsx 📈 +3 B (+0.06%) 4.59 kB → 4.59 kB
src/components/transactions/TransactionsTable.jsx 📈 +38 B (+0.06%) 67.37 kB → 67.41 kB
locale/en.json 📈 +27 B (+0.03%) 96.69 kB → 96.72 kB
src/components/reports/reports/Calendar.tsx 📉 -37 B (-0.14%) 26.11 kB → 26.07 kB
src/components/admin/UserDirectory/UserDirectory.tsx 📉 -25 B (-0.27%) 9.16 kB → 9.13 kB
src/components/App.tsx 📉 -26 B (-0.43%) 5.84 kB → 5.81 kB
src/components/modals/OpenIDEnableModal.tsx 📉 -18 B (-0.57%) 3.08 kB → 3.07 kB
src/components/transactions/TransactionList.jsx 📉 -38 B (-0.71%) 5.22 kB → 5.18 kB
home/runner/work/actual/actual/packages/loot-core/src/client/constants.ts 📉 -104 B (-8.03%) 1.26 kB → 1.16 kB
home/runner/work/actual/actual/packages/loot-core/src/client/reducers/index.ts 📉 -21 B (-11.41%) 184 B → 163 B
home/runner/work/actual/actual/packages/loot-core/src/client/actions/account.ts 🔥 -5.86 kB (-100%) 5.86 kB → 0 B
home/runner/work/actual/actual/packages/loot-core/src/client/reducers/account.ts 🔥 -662 B (-100%) 662 B → 0 B
src/hooks/useActions.ts 🔥 -173 B (-100%) 173 B → 0 B
View detailed bundle breakdown

Added

Asset File Size % Changed
static/js/pt-BR.js 0 B → 19 B (+19 B) -
static/js/nb-NO.js 0 B → 19 B (+19 B) -
static/js/cy.js 0 B → 17 B (+17 B) -

Removed

No assets were removed

Bigger

Asset File Size % Changed
static/js/index.js 3.74 MB → 3.78 MB (+36.91 kB) +0.96%
static/js/fr.js 12.6 kB → 13.11 kB (+516 B) +4.00%
static/js/wide.js 105.92 kB → 106.04 kB (+126 B) +0.12%
static/js/en.js 96.69 kB → 96.72 kB (+27 B) +0.03%

Smaller

Asset File Size % Changed
static/js/AppliedFilters.js 10.24 kB → 10.21 kB (-38 B) -0.36%
static/js/ReportRouter.js 1.58 MB → 1.58 MB (-37 B) -0.00%

Unchanged

Asset File Size % Changed
static/js/pl.js 17 B 0%
static/js/ta.js 17 B 0%
static/js/zh-Hant.js 10.13 kB 0%
static/js/es.js 3.45 kB 0%
static/js/indexeddb-main-thread-worker-e59fee74.js 13.5 kB 0%
static/js/workbox-window.prod.es5.js 5.69 kB 0%
static/js/de.js 4.59 kB 0%
static/js/resize-observer.js 18.37 kB 0%
static/js/uk.js 120.47 kB 0%
static/js/BackgroundImage.js 122.29 kB 0%
static/js/useAccountPreviewTransactions.js 1.63 kB 0%
static/js/narrow.js 84.3 kB 0%

Copy link
Contributor

github-actions bot commented Dec 19, 2024

Bundle Stats — loot-core

Hey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle.

As this PR is updated, I'll keep you updated on how the bundle size is impacted.

Total

Files count Total bundle size % Changed
1 1.33 MB 0%

Changeset

No files were changed

View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

No assets were bigger

Smaller

No assets were smaller

Unchanged

Asset File Size % Changed
kcab.worker.js 1.33 MB 0%

@joel-jeremy joel-jeremy changed the base branch from master to redux-toolkit December 19, 2024 21:58
@joel-jeremy
Copy link
Contributor Author

/update-vrt

@@ -289,7 +300,8 @@ type AccountInternalProps = {
categoryGroups: ReturnType<typeof useCategories>['grouped'];
hideFraction: boolean;
accountsSyncing: string[];
} & ReturnType<typeof useActions>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the useActions prop here. Instead we will pass in the dispatch function and dispatch the actions manually instead.


export function listenForSyncEvent(actions, store) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove actions from useActions here. Instead, we manually dispatch the actions through the store's dispatch property.

Copy link
Contributor

👋 Hi! It looks like this PR has not had any changes for a week now. Would you like someone to review this PR? If so - please remove the "[WIP]" prefix from the PR title. That will let the community know that this PR is open for a review.

@github-actions github-actions bot added the Stale label Dec 28, 2024
@joel-jeremy joel-jeremy removed the Stale label Jan 1, 2025
@joel-jeremy joel-jeremy force-pushed the redux-toolkit branch 2 times, most recently from f7c923c to 8ce85f8 Compare January 6, 2025 18:08
@joel-jeremy joel-jeremy force-pushed the redux-toolkit-createSlice branch from e8d550e to ace372d Compare January 6, 2025 18:30
@joel-jeremy joel-jeremy changed the base branch from redux-toolkit to rename-redux-hooks January 6, 2025 18:30
@joel-jeremy joel-jeremy force-pushed the redux-toolkit-createSlice branch from ace372d to d673a92 Compare January 7, 2025 16:55
@joel-jeremy joel-jeremy force-pushed the redux-toolkit-createSlice branch from ca9c7a6 to 8d4efb2 Compare January 7, 2025 22:19
@joel-jeremy joel-jeremy force-pushed the redux-toolkit-createSlice branch from 8d4efb2 to 7c128b1 Compare January 7, 2025 22:21
@joel-jeremy joel-jeremy force-pushed the redux-toolkit-createSlice branch from 432ab92 to 5ed4eb0 Compare January 8, 2025 07:34
@joel-jeremy joel-jeremy changed the title [WIP] [Redux Toolkit Migration] accountSlice [WIP] [Redux Toolkit Migration] accountsSlice Jan 8, 2025
@joel-jeremy joel-jeremy force-pushed the redux-toolkit-createSlice branch from f498538 to 968172c Compare January 8, 2025 08:14
@joel-jeremy joel-jeremy force-pushed the redux-toolkit-createSlice branch from 73617c3 to 71aebf5 Compare January 13, 2025 16:57
@joel-jeremy
Copy link
Contributor Author

joel-jeremy commented Jan 13, 2025

I hate to be the person to ask, but would you please break out this PR into many smaller ones? I can't even review it currently as Github crashes when opening the "files" tab. But besides that: we should strive for many smaller PRs as opposed to these mega PRs as they are significantly harder to review and thus more likely to accidentally break things.

In the brief skimming I did here before my browser crashed - I noticed a few distinct changes that ideally should be separated out - 1) renaming of account state slice to accounts (why?); 2) refactors of actions to use useDispatch (nice); 3) account selectors, reducers and types getting unified in a single file as opposed to smaller files (why?).

  • would you please break out this PR into many smaller ones
    • I have broken down the PRs per feature slice which atleast helps group together the changes. I could look into how to break up the subsequent PRs into even smaller chunks without consuming too much time.
    • (Edit) It's hard to break it down even further because changing the reducer requires changing the actions to use those new reducers. I tried that approach (separate PR for reducers and actions) early on and had to settled on the current PRs instead.
  1. renaming of account state slice to accounts (why?)
    • This is to make the state naming consistent, other state names are plural. I have reverted the change for now.
  2. refactors of actions to use useDispatch (nice)
    • I would like to keep this change here if possible. It's more change upfront but less change on the subsequent PRs.
  3. account selectors, reducers and types getting unified in a single file as opposed to smaller files (why?)
    • It is recommended by Redux to colocate reducers and actions so that's what I initially done here, we could split them under each feature folder in the future if we want

MatissJanis
MatissJanis previously approved these changes Jan 13, 2025
type RuleConditionEntity,
type TransactionEntity,
type TransactionFilterEntity,
type PayeeEntity,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥜 nitpick: previous import location would be more correct (sorted alphabetically). I hope one day we can enforce this with eslint & auto fix with prettier.‏

@@ -145,6 +146,7 @@ export function ImportTransactionsModal({ options }) {
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
const [prefs, savePrefs] = useSyncedPrefs();
const dispatch = useDispatch();
const categories = useCategories();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 praise: ‏much cleaner! nice

@@ -368,6 +373,101 @@ export function forceCloseAccount(accountId) {
return closeAccount(accountId, null, null, true);
}

// Remember the last transaction manually added to the system
export function setLastTransaction(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-open-source code, so sadly I won't know the reason here. But I would venture a guess that the rationale was - whatever was quicker & easier at the time without much thought for scaling/clean code.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
packages/desktop-client/src/components/modals/OpenIDEnableModal.tsx (1)

Consider adding OpenID configuration validation before server submission

The OpenID configuration contains sensitive fields (client_id, client_secret) and URLs (issuer, server_hostname) that should be validated before submission. While TypeScript ensures the presence of required fields, you should add client-side validation to verify:

  • URL format for issuer and server_hostname
  • Non-empty and properly formatted client_id and client_secret
  • Valid selectedProvider value

This validation will improve security and provide better user feedback before the server request.

🔗 Analysis chain

Line range hint 33-52: Consider adding OpenID configuration validation.

The onSave function sends the OpenID configuration directly to the server. Consider adding client-side validation of the OpenID configuration format before making the server request to prevent potential security issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if OpenID configuration validation exists elsewhere
rg -A 5 "OpenIdConfig.*validate" --type typescript

Length of output: 87


Script:

#!/bin/bash
# Search for OpenIdConfig type definition and validation patterns
rg -t ts -t tsx "type OpenIdConfig" -A 5
rg -t ts -t tsx "interface OpenIdConfig" -A 5

# Search for any validation related to OpenID
rg -t ts -t tsx "(validate|check|verify).*openid" -i -A 3

# Look for any utility functions that might handle OpenID configuration
rg -t ts -t tsx "function.*openid.*config" -i -A 5

Length of output: 315


Script:

#!/bin/bash
# Search for OpenIdConfig type and interface definitions
ast-grep --pattern 'type OpenIdConfig = {
  $$$
}'

ast-grep --pattern 'interface OpenIdConfig {
  $$$
}'

# Search with correct file extension patterns
rg "type OpenIdConfig|interface OpenIdConfig" --glob "*.{ts,tsx}" -A 5
rg "(validate|check|verify).*openid" --glob "*.{ts,tsx}" -i -A 3
rg "function.*openid.*config" --glob "*.{ts,tsx}" -i -A 5

Length of output: 4482

♻️ Duplicate comments (1)
packages/loot-core/src/client/accounts/accountsSlice.ts (1)

67-76: ⚠️ Potential issue

Add error handling to async thunks.

All async thunks should handle potential errors to prevent unhandled promise rejections and maintain consistent state. This was previously flagged in earlier reviews but hasn't been addressed.

Apply this pattern to all thunks:

 export const unlinkAccount = createAppAsyncThunk(
   'accounts/unlinkAccount',
-  async ({ id }: UnlinkAccountPayload, { dispatch }) => {
+  async ({ id }: UnlinkAccountPayload, { dispatch, rejectWithValue }) => {
     const { markAccountSuccess } = accountsSlice.actions;

-    await send('account-unlink', { id });
-    dispatch(markAccountSuccess({ id }));
-    await dispatch(getAccounts());
+    try {
+      await send('account-unlink', { id });
+      dispatch(markAccountSuccess({ id }));
+      await dispatch(getAccounts());
+    } catch (error) {
+      return rejectWithValue(error);
+    }
   },
 );

Also applies to: 85-100, 108-122, 197-298, 305-312

🧹 Nitpick comments (15)
packages/desktop-client/src/components/modals/EditAccess.tsx (3)

67-81: Consider extracting notification configuration to a factory.

While the notification dispatch logic is correct, consider extracting the notification configuration to a dedicated factory function to improve maintainability and reusability.

// Create a notification factory
const createLoginExpiredNotification = (t: TFunction, onSignOut: () => void) => ({
  type: 'error' as const,
  id: 'login-expired',
  title: t('Login expired'),
  sticky: true,
  message: getUserAccessErrors('token-expired'),
  button: {
    title: t('Go to login'),
    action: onSignOut,
  },
});

// Usage in component
dispatch(addNotification(createLoginExpiredNotification(t, () => dispatch(signOut()))));

141-141: Consider creating a custom hook for modal actions.

While the popModal dispatch is correct, consider creating a custom hook to encapsulate modal-related actions for better reusability across components.

// Create a custom hook
const useModalActions = () => {
  const dispatch = useDispatch();
  return {
    closeModal: () => dispatch(popModal()),
    // Add other modal-related actions here
  };
};

// Usage in component
const { closeModal } = useModalActions();
// Then use: onPress={closeModal}

Line range hint 1-155: Consider splitting the component into presentation and container components.

While the current implementation is solid, the component handles both UI rendering and business logic. Consider splitting it into:

  1. A presentational component handling only the UI
  2. A container component managing the Redux and business logic

This would improve testability and maintainability.

packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx (1)

148-169: Consider extracting notification structure for reusability.

The notification structure is duplicated between error cases. Consider extracting it to a helper function to improve maintainability.

+ const createErrorNotification = (title: string, message: string, button?: { title: string; action: () => void }) => ({
+   type: 'error' as const,
+   sticky: true,
+   title,
+   message,
+   ...(button && { button }),
+ });

  if (error === 'token-expired') {
    dispatch(
-     addNotification({
-       type: 'error',
-       id: 'login-expired',
-       title: t('Login expired'),
-       sticky: true,
-       message: getUserDirectoryErrors(error),
-       button: {
-         title: t('Go to login'),
-         action: () => dispatch(signOut()),
-       },
-     })
+     addNotification({
+       ...createErrorNotification(
+         t('Login expired'),
+         getUserDirectoryErrors(error),
+         {
+           title: t('Go to login'),
+           action: () => dispatch(signOut()),
+         }
+       ),
+       id: 'login-expired',
+     })
    );
  } else {
    dispatch(
-     addNotification({
-       type: 'error',
-       title: t('Something happened while deleting users'),
-       sticky: true,
-       message: getUserDirectoryErrors(error),
-     })
+     addNotification(
+       createErrorNotification(
+         t('Something happened while deleting users'),
+         getUserDirectoryErrors(error)
+       )
+     )
    );
  }
packages/loot-core/src/client/shared-listeners.ts (3)

180-182: Consider combining multiple dispatches.

These consecutive dispatches could be combined into a single thunk action for better atomicity and error handling.

-                await store.dispatch(uploadBudget());
-                store.dispatch(sync());
-                store.dispatch(loadPrefs());
+                await store.dispatch(async (dispatch) => {
+                  await dispatch(uploadBudget());
+                  await dispatch(sync());
+                  await dispatch(loadPrefs());
+                });

247-253: Consider extracting notification configuration.

The notification configurations are repeated across multiple error cases. Consider extracting them into separate constants or configuration objects to improve maintainability.

const ENCRYPTION_NOTIFICATIONS = {
  missingKey: {
    title: t('Missing encryption key'),
    message: t('Unable to encrypt your data because you are missing the key. Create your key to sync your data.'),
    button: {
      title: t('Create key'),
      // ... rest of the configuration
    }
  },
  // ... other encryption-related notifications
};

Line range hint 65-321: Consider breaking down the error handling switch statement.

The error handling switch statement is quite large and complex. Consider breaking it down into smaller, more focused functions for better maintainability and testing.

// Example refactor
const handleSyncError = (event: SyncErrorEvent, store: AppStore) => {
  const handlers = {
    'out-of-sync': handleOutOfSyncError,
    'file-old-version': handleOldVersionError,
    // ... other handlers
  };
  
  const handler = handlers[event.subtype];
  if (handler) {
    const notification = handler(event, store);
    if (notification) {
      store.dispatch(addNotification({ type: 'error', ...notification }));
    }
  }
};
packages/loot-core/src/client/actions/queries.ts (4)

395-414: Consider extracting shared error handling logic.

The error handling logic is duplicated between importPreviewTransactions and importTransactions. Consider extracting it into a shared utility function.

+function dispatchImportErrors(dispatch: AppDispatch, errors: Array<{ message: string }>) {
+  errors.forEach(error => {
+    dispatch(
+      addNotification({
+        type: 'error',
+        message: error.message,
+      }),
+    );
+  });
+}

 export function importPreviewTransactions(id: string, transactions) {
   return async (dispatch: AppDispatch): Promise<boolean> => {
     const { errors = [], updatedPreview } = await send('transactions-import', {
       accountId: id,
       transactions,
       isPreview: true,
     });

-    errors.forEach(error => {
-      dispatch(
-        addNotification({
-          type: 'error',
-          message: error.message,
-        }),
-      );
-    });
+    dispatchImportErrors(dispatch, errors);

     return updatedPreview;
   };
 }

Also applies to: 416-455


416-426: Document the reconciliation flag's purpose.

The reconcile parameter's default value and its impact on the import process should be documented for better maintainability.

-export function importTransactions(id: string, transactions, reconcile = true) {
+/**
+ * Import transactions with optional reconciliation.
+ * @param id - The account ID
+ * @param transactions - The transactions to import
+ * @param reconcile - When true, performs transaction matching and updates existing transactions.
+ *                   When false, directly adds transactions without matching. Defaults to true.
+ */
+export function importTransactions(id: string, transactions, reconcile = true) {

427-454: Consider adding type safety for error messages.

The error handling assumes a specific shape for error objects. Consider adding type definitions for the API response to ensure type safety.

+interface ImportError {
+  message: string;
+}
+
+interface ImportResponse {
+  errors?: ImportError[];
+  added: unknown[];
+  updated: unknown[];
+}
+
 const {
   errors = [],
   added,
   updated,
-} = await send('transactions-import', {
+}: ImportResponse = await send('transactions-import', {

Line range hint 1-470: Consider full Redux Toolkit migration for this file.

While the changes are well-implemented, they don't fully embrace Redux Toolkit patterns. Consider:

  1. Using createSlice to automatically generate action creators
  2. Eliminating the need for separate constants
  3. Co-locating actions with reducers in a slice file

This would align better with the PR's objective of migrating to Redux Toolkit.

packages/loot-core/src/client/accounts/accountsSlice.ts (1)

10-15: Consider renaming the slice to match the file name.

The slice name is 'account' (singular) while the file name is 'accountsSlice' (plural). Consider using consistent naming to improve maintainability.

-const sliceName = 'account';
+const sliceName = 'accounts';
packages/desktop-client/src/components/modals/OpenIDEnableModal.tsx (1)

76-76: Consider using useCallback for the modal dismiss handler.

While the dispatch implementation is correct, consider memoizing the handler with useCallback to prevent unnecessary re-renders:

+ const handleModalDismiss = useCallback(() => dispatch(popModal()), [dispatch]);

  // Then in JSX:
- onPress={() => dispatch(popModal())}
+ onPress={handleModalDismiss}
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (2)

Line range hint 236-247: Refactor category parsing for better reusability

The parseCategoryFields function could be refactored to be more reusable and handle edge cases better.

Consider this implementation:

-function parseCategoryFields(trans, categories) {
-  let match = null;
-  categories.forEach(category => {
-    if (category.id === trans.category) {
-      return null;
-    }
-    if (category.name === trans.category) {
-      match = category.id;
-    }
-  });
-  return match;
+function parseCategoryFields(trans, categories) {
+  if (!trans.category) return null;
+  
+  // First check if it's already an ID
+  const byId = categories.find(category => category.id === trans.category);
+  if (byId) return byId.id;
+  
+  // Then check by name
+  const byName = categories.find(category => category.name === trans.category);
+  return byName?.id || null;
}

Line range hint 445-445: Consider memoizing category lookups

The repeated category lookups in the getImportPreview function could benefit from memoization to improve performance when dealing with large transaction sets.

Consider creating a memoized category map:

+const categoryMap = useMemo(
+  () => new Map(categories.list.map(cat => [cat.id, cat])),
+  [categories.list]
+);

// Then use it in the preview logic
-existing_trx.category = categories.list.find(
-  cat => cat.id === existing_trx.category,
-)?.name;
+existing_trx.category = categoryMap.get(existing_trx.category)?.name;

Also applies to: 446-446

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73617c3 and f123633.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/4012.md is excluded by !**/*.md
📒 Files selected for processing (33)
  • packages/desktop-client/src/components/App.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Account.tsx (18 hunks)
  • packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx (2 hunks)
  • packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx (3 hunks)
  • packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx (2 hunks)
  • packages/desktop-client/src/components/modals/EditAccess.tsx (5 hunks)
  • packages/desktop-client/src/components/modals/EditUser.tsx (7 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (3 hunks)
  • packages/desktop-client/src/components/modals/OpenIDEnableModal.tsx (4 hunks)
  • packages/desktop-client/src/components/modals/PasswordEnableModal.tsx (5 hunks)
  • packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx (3 hunks)
  • packages/desktop-client/src/components/modals/TransferOwnership.tsx (5 hunks)
  • packages/desktop-client/src/components/reports/reports/Calendar.tsx (0 hunks)
  • packages/desktop-client/src/components/sidebar/Accounts.tsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (0 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3 hunks)
  • packages/desktop-client/src/global-events.ts (6 hunks)
  • packages/desktop-client/src/hooks/useActions.ts (0 hunks)
  • packages/desktop-client/src/index.tsx (2 hunks)
  • packages/loot-core/src/client/accounts/accountsSlice.ts (1 hunks)
  • packages/loot-core/src/client/actions/account.ts (0 hunks)
  • packages/loot-core/src/client/actions/budgets.ts (1 hunks)
  • packages/loot-core/src/client/actions/index.ts (0 hunks)
  • packages/loot-core/src/client/actions/queries.ts (2 hunks)
  • packages/loot-core/src/client/actions/sync.ts (2 hunks)
  • packages/loot-core/src/client/reducers/account.ts (0 hunks)
  • packages/loot-core/src/client/reducers/index.ts (0 hunks)
  • packages/loot-core/src/client/redux/index.ts (1 hunks)
  • packages/loot-core/src/client/shared-listeners.ts (13 hunks)
  • packages/loot-core/src/client/state-types/account.d.ts (0 hunks)
  • packages/loot-core/src/client/state-types/index.d.ts (0 hunks)
  • packages/loot-core/src/client/store/index.ts (3 hunks)
  • packages/loot-core/src/client/store/mock.ts (1 hunks)
💤 Files with no reviewable changes (9)
  • packages/loot-core/src/client/actions/index.ts
  • packages/loot-core/src/client/reducers/index.ts
  • packages/desktop-client/src/hooks/useActions.ts
  • packages/loot-core/src/client/state-types/index.d.ts
  • packages/desktop-client/src/components/reports/reports/Calendar.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/loot-core/src/client/reducers/account.ts
  • packages/loot-core/src/client/state-types/account.d.ts
  • packages/loot-core/src/client/actions/account.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • packages/loot-core/src/client/redux/index.ts
  • packages/desktop-client/src/components/App.tsx
  • packages/loot-core/src/client/actions/sync.ts
  • packages/loot-core/src/client/store/index.ts
  • packages/desktop-client/src/components/modals/TransferOwnership.tsx
  • packages/desktop-client/src/components/modals/PasswordEnableModal.tsx
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx
  • packages/desktop-client/src/components/sidebar/Accounts.tsx
  • packages/loot-core/src/client/actions/budgets.ts
  • packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx
  • packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx
  • packages/desktop-client/src/components/admin/UserAccess/UserAccessRow.tsx
  • packages/desktop-client/src/index.tsx
  • packages/desktop-client/src/components/modals/EditUser.tsx
  • packages/loot-core/src/client/store/mock.ts
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/accounts/Account.tsx (2)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/accounts/Account.tsx:655-665
Timestamp: 2024-11-10T16:45:31.225Z
Learning: The Account component in 'packages/desktop-client/src/components/accounts/Account.tsx' is being rewritten in a separate PR.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-11-10T16:45:25.627Z
Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Visual regression
  • GitHub Check: Functional
  • GitHub Check: build (ubuntu-latest)
🔇 Additional comments (22)
packages/desktop-client/src/components/modals/EditAccess.tsx (2)

4-4: LGTM! Clean import restructuring.

The import changes align well with the Redux Toolkit migration strategy, maintaining a clear separation of concerns.

Also applies to: 10-10


30-30: LGTM! Proper hook implementation.

The useDispatch hook is correctly initialized and the useEffect dependency array is appropriately simplified.

Also applies to: 53-53

packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx (3)

13-13: LGTM! Clean import refactoring.

The direct import of specific actions aligns with the Redux Toolkit migration objectives and follows best practices by importing only what's needed.


148-169: LGTM! Robust error handling implementation.

The error handling logic is well-structured with appropriate sticky notifications and clear error messages. The token expiration case is properly handled with a sign-out option.


176-183: LGTM! Complete and accurate dependencies array.

All necessary dependencies are properly included in the useCallback hook's dependencies array.

packages/loot-core/src/client/shared-listeners.ts (3)

6-18: LGTM! Clean import organization.

The explicit imports for individual actions and proper TypeScript type imports improve code clarity and type safety.

Also applies to: 20-20


22-22: LGTM! Improved function signature.

The simplified signature with proper TypeScript typing aligns well with Redux Toolkit patterns and improves type safety.


36-64: LGTM! Clean dispatch implementation for sync success events.

The implementation maintains clear separation of concerns while properly handling different table updates through store dispatch.

packages/loot-core/src/client/actions/queries.ts (2)

8-12: LGTM! Clean type imports.

The new TypeScript type imports are well-organized and follow proper naming conventions.


457-469: LGTM! Well-structured action creators.

The account management action creators are concise, properly typed, and follow Redux best practices.

packages/desktop-client/src/global-events.ts (2)

2-16: LGTM! Clean refactor of imports and function signature.

The changes improve code organization by:

  1. Centralizing action creators in a dedicated module
  2. Simplifying the function signature by removing the redundant actions parameter

Also applies to: 21-21


Line range hint 22-164: LGTM! Consistent refactor of event handlers.

The changes systematically replace direct action calls with store.dispatch, improving code consistency and maintainability while preserving the existing functionality.

packages/loot-core/src/client/accounts/accountsSlice.ts (2)

17-36: LGTM! Well-structured TypeScript types.

The payload types are well-defined, providing strong type safety and clear interfaces for the actions.


38-61: LGTM! Clean reducer implementations.

The reducers are concise and leverage Redux Toolkit's Immer for safe state mutations. Each reducer has a single responsibility and is well-typed.

packages/desktop-client/src/components/accounts/Account.tsx (3)

16-31: LGTM! Well-organized imports.

The imports are properly structured, bringing in the necessary action creators and Redux hooks for the migration.

Also applies to: 85-85


304-305: Consider deferring non-essential changes.

Adding the dispatch prop is necessary for the Redux migration. However, since this component is being rewritten in a separate PR, consider deferring any non-essential changes to avoid complicating the future rewrite.


438-438: LGTM! Consistent Redux migration.

The changes systematically replace direct action calls with dispatch, maintaining consistency throughout the component. The migration follows Redux patterns and best practices.

Also applies to: 504-504, 626-628, 646-658, 690-690, 753-758, 784-784, 812-826, 829-829, 832-832, 1216-1223, 1847-1851, 1907-1907, 1918-1918

packages/desktop-client/src/components/modals/OpenIDEnableModal.tsx (2)

4-4: LGTM! Clean migration to Redux dispatch.

The changes properly set up Redux dispatch functionality, replacing the custom useActions hook.

Also applies to: 10-10, 28-28


41-41: Verify error handling after dispatch migration.

The dispatch implementation looks correct. However, let's verify that error handling remains consistent across similar patterns in the codebase.

✅ Verification successful

Error handling implementation verified as correct

The error handling in OpenIDEnableModal matches established patterns across the codebase, particularly in security-sensitive components like PasswordEnableModal. The try-catch implementation is consistent with similar critical operations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for similar patterns of closeBudget usage with error handling
rg -A 5 "dispatch\(closeBudget\(\)\)" --type typescript

Length of output: 92


Script:

#!/bin/bash
# Search for closeBudget patterns in ts/tsx files
rg -t ts -t tsx -A 5 "closeBudget\(\)" 

# Search specifically for try-catch blocks around closeBudget
ast-grep --pattern 'try {
  $$$
  closeBudget()
  $$$
} catch'

Length of output: 133


Script:

#!/bin/bash
# Find all TypeScript files that contain closeBudget
fd "\.(ts|tsx)$" | xargs grep -l "closeBudget"

# Search for closeBudget usage patterns
grep -r "closeBudget" . --include="*.ts" --include="*.tsx" -A 5

Length of output: 15785

packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (3)

14-14: LGTM! Clean hook organization

The addition of the useCategories hook import follows the established pattern of organizing custom hooks at the top of the file.


149-149: LGTM! Improved state management

Good refactor to move categories management from props to a centralized hook, making the component more maintainable and reducing prop drilling.

Also applies to: 162-162


Line range hint 236-236: Verify category parsing logic

The category parsing logic in both parseCategoryFields and getImportPreview functions needs verification to ensure it handles all edge cases correctly.

Let's verify the category parsing implementation across the codebase:

Also applies to: 445-445

✅ Verification successful

Category parsing implementation verified as robust and secure

The category parsing logic in both parseCategoryFields and getImportPreview functions properly handles all edge cases through:

  • Explicit null checks before category assignments
  • Support for both ID and name-based category matching
  • Consistent implementation across the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for similar category parsing implementations and potential edge cases

# Look for category parsing patterns
ast-grep --pattern 'categories.list.find(cat => cat.id === $_)'

# Look for potential null/undefined handling
rg -A 3 'category.*=.*categories\.list'

Length of output: 2210


Script:

#!/bin/bash
# Find parseCategoryFields implementation and related tests
ast-grep --pattern 'function parseCategoryFields($_) { $$$ }'

# Look for test files
fd -e test.js -e test.jsx -e spec.js -e spec.jsx

# Search for category-related test cases
rg -l "test.*category" --type js --type jsx

Length of output: 985


Script:

#!/bin/bash
# Search for parseCategoryFields implementation
rg -t js -t jsx "parseCategoryFields" -A 10

# Look for category-related utility functions and tests
rg -t js -t jsx "function.*category" -A 5

# Search for category parsing in test files
rg -t js -t jsx "test.*category" -A 5 "packages/desktop-client/src"

Length of output: 239


Script:

#!/bin/bash
# Find files containing parseCategoryFields
rg -l "parseCategoryFields"

# Search for the implementation
rg "function.*parseCategoryFields" -A 15

# Look for related category parsing code
rg "category.*parse|parse.*category" -A 5

Length of output: 4340

@joel-jeremy joel-jeremy merged commit 6573a52 into master Jan 13, 2025
20 checks passed
@joel-jeremy joel-jeremy deleted the redux-toolkit-createSlice branch January 13, 2025 22:42
@matt-fidd
Copy link
Contributor

Not sure if this is only an error until the next PRs make their way in but I'm getting this error a fair amount on edge:

Uncaught (in promise) Error: A state mutation was detected between dispatches, in the path 'queries.categories.list.3'.  This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)
    at loadCategories (index.tsx:88:5)
    at run (index.tsx:93:7)
    at index.tsx:108:5

I can't find a way to reliably reproduce but if you click around enough it seems to trigger it

It's caused a couple of crashes, showing the Fatal Error modal

@joel-jeremy
Copy link
Contributor Author

Not sure if this is only an error until the next PRs make their way in but I'm getting this error a fair amount on edge:

Uncaught (in promise) Error: A state mutation was detected between dispatches, in the path 'queries.categories.list.3'.  This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)
    at loadCategories (index.tsx:88:5)
    at run (index.tsx:93:7)
    at index.tsx:108:5

I can't find a way to reliably reproduce but if you click around enough it seems to trigger it

It's caused a couple of crashes, showing the Fatal Error modal

I believe this error is related to this https://github.com/actualbudget/actual/pull/4016/files#diff-c11c76733466c581e1797004e5de127d5731d4a23b6d9f02a111fbb390dcf6a8R299. The categories state is being modified in-place by the categories.list.sort call - the fix is to make a copy and sort on that copy. Can you please check to see if the error still appears on that PRs demo?

@matt-fidd
Copy link
Contributor

Can you please check to see if the error still appears on that PRs demo?

That looks to have fixed it, I can't make it happen on the demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants