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

Migrate Account component to a functional component and use the new useTransactions hook #3708

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

joel-jeremy
Copy link
Contributor

@joel-jeremy joel-jeremy commented Oct 21, 2024

Follow up to #3685 to simplify fetching transactions throughout the codebase.

This PR is migrating the desktop Account component/page to a functional component so that it can use the new useTransactions hook from #3685

This is a major rewrite of the component so I would like to get 2-3 approvals for this before merging

@actual-github-bot actual-github-bot bot changed the title Accounts function component [WIP] Accounts function component Oct 21, 2024
Copy link

netlify bot commented Oct 21, 2024

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit 45095cf
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/67338adaba82e200089ac36e
😎 Deploy Preview https://deploy-preview-3708.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 Oct 21, 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
9 5.35 MB → 5.35 MB (-8.26 kB) -0.15%
Changeset
File Δ Size
src/components/accounts/Balance.jsx 📈 +204 B (+3.67%) 5.42 kB → 5.62 kB
src/components/schedules/ScheduleLink.tsx 📈 +67 B (+1.79%) 3.65 kB → 3.71 kB
src/components/accounts/Header.tsx 📈 +207 B (+1.10%) 18.34 kB → 18.55 kB
src/components/transactions/SelectedTransactionsButton.tsx 📈 +34 B (+0.42%) 7.9 kB → 7.93 kB
home/runner/work/actual/actual/packages/loot-core/src/client/data-hooks/schedules.tsx 📈 +10 B (+0.32%) 3.01 kB → 3.02 kB
src/components/accounts/Reconcile.tsx 📈 +11 B (+0.21%) 5.19 kB → 5.2 kB
src/components/table.tsx 📈 +49 B (+0.20%) 24.17 kB → 24.22 kB
src/components/schedules/SchedulesTable.tsx 📈 +8 B (+0.07%) 11.81 kB → 11.82 kB
src/components/transactions/TransactionsTable.jsx 📈 +24 B (+0.04%) 66.42 kB → 66.44 kB
src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx 📉 -1 B (-0.00%) 30.12 kB → 30.12 kB
src/components/transactions/TransactionList.jsx 📉 -17 B (-0.32%) 5.12 kB → 5.11 kB
src/components/spreadsheet/useSheetValue.ts 📉 -10 B (-0.86%) 1.14 kB → 1.13 kB
node_modules/usehooks-ts/dist/index.js 📉 -357 B (-5.57%) 6.26 kB → 5.91 kB
src/hooks/useSelected.tsx 📉 -458 B (-6.00%) 7.45 kB → 7 kB
src/components/accounts/Account.tsx 📉 -6.53 kB (-14.66%) 44.53 kB → 38 kB
src/hooks/usePreviewTransactions.ts 🔥 -1.5 kB (-100%) 1.5 kB → 0 B
View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

No assets were bigger

Smaller

Asset File Size % Changed
static/js/wide.js 241.96 kB → 234.38 kB (-7.58 kB) -3.13%
static/js/index.js 3.37 MB → 3.37 MB (-692 B) -0.02%

Unchanged

Asset File Size % Changed
static/js/indexeddb-main-thread-worker-e59fee74.js 13.5 kB 0%
static/js/resize-observer.js 18.37 kB 0%
static/js/workbox-window.prod.es5.js 5.69 kB 0%
static/js/BackgroundImage.js 122.29 kB 0%
static/js/narrow.js 82.76 kB 0%
static/js/AppliedFilters.js 21.3 kB 0%
static/js/ReportRouter.js 1.49 MB 0%

Copy link
Contributor

github-actions bot commented Oct 21, 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.27 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.27 MB 0%

@joel-jeremy joel-jeremy changed the title [WIP] Accounts function component Migrate Account component to a functional component and use the new useTransactions hook Oct 21, 2024
Copy link
Contributor

coderabbitai bot commented Oct 21, 2024

Warning

Rate limit exceeded

@joel-jeremy has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 48 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6898f76 and 45095cf.

Walkthrough

The pull request introduces a series of modifications across multiple components in the desktop client, primarily focusing on state management, parameter naming, and loading state handling. Key changes include the replacement of useHover with css from @emotion/css, the removal of useRef in favor of useMemo, and updates to function signatures to improve clarity, such as changing account to accountId in several components. The Balances component has been enhanced with new props for filtered balances and loading states. Additionally, the ImportTransactionsModal component now integrates the useCategories hook to streamline category management. Other components, including TransactionList and TransactionsTable, have been updated to handle loading states more effectively. Overall, the changes improve the structure and functionality of the components, enhancing type safety and ensuring better state management.

Possibly related PRs

Suggested labels

sparkles: Merged

Suggested reviewers

  • youngcw
  • matt-fidd

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 17

🧹 Outside diff range and nitpick comments (41)
packages/desktop-client/src/hooks/useNotes.ts (1)

Line range hint 8-12: LGTM: Function updated to use useQuery correctly.

The changes successfully migrate the useNotes function to use the new useQuery hook. The query logic remains intact, and the use of useMemo for deriving the note content is a good practice for performance optimization.

Consider adding a comment explaining the useMemo logic for better clarity:

return useMemo(
  // Return the first note's content if available, otherwise null
  () => (data && data.length > 0 ? data[0].note : null),
  [data]
);
packages/loot-core/src/client/data-hooks/widget.ts (1)

8-10: Approved with a minor suggestion.

The implementation change to useQuery is consistent with the import modification and PR objectives. The inclusion of both id and type in the dependency array enhances the hook's reactivity, which is a good improvement.

Consider memoizing the query function to optimize performance:

const queryFn = useCallback(() => q('dashboard').filter({ id, type }).select('*'), [id, type]);
const { data = [], isLoading } = useQuery<W>(queryFn, [id, type]);

This change can prevent unnecessary re-creation of the query function on each render.

packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1)

11-13: LGTM: Excellent addition for improved synchronization.

The new waitFor method is a great addition that ensures the account list is fully loaded before any interactions occur. This aligns well with the PR objective and improves test reliability.

Consider adding a timeout and error handling to make the wait more robust:

 async waitFor() {
-  await this.accountList.waitFor();
+  try {
+    await this.accountList.waitFor({ timeout: 5000 });
+  } catch (error) {
+    throw new Error('Account list did not load within the expected time');
+  }
 }
packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)

19-19: LGTM: Hook usage updated correctly with a minor suggestion.

The destructuring of the useSchedules hook has been appropriately updated to align with its new structure. The default value for schedules is a good practice to prevent potential issues.

Consider using TypeScript's non-null assertion operator if you're certain that schedules will never be undefined:

const { schedules!, isLoading } = useSchedules();

This would eliminate the need for the default value while still ensuring type safety. However, only use this if you're absolutely sure that schedules will always be defined when not loading.


21-23: LGTM: Loading state handled correctly with a suggestion for enhancement.

The addition of the isLoading check prevents rendering the component when data is still loading, which is a good practice for improving user experience.

Consider enhancing the user experience further by showing a loading indicator instead of returning null. This would provide visual feedback to the user that data is being fetched. Here's an example:

if (isLoading) {
  return <LoadingSpinner />; // Assuming you have a LoadingSpinner component
}

This change would make the loading state more apparent to the user, improving the overall user experience.

packages/desktop-client/e2e/page-models/mobile-account-page.js (1)

36-38: LGTM! Consider adding error handling.

The clearSearch() method is a useful addition that provides a clean way to reset the search state. This is particularly valuable for test setup and teardown processes.

Consider adding a try-catch block to handle potential errors when clearing the search box. This could improve the robustness of the method:

async clearSearch() {
  try {
    await this.searchBox.clear();
  } catch (error) {
    console.error('Failed to clear search box:', error);
    throw error;
  }
}
packages/loot-core/src/client/data-hooks/reports.ts (1)

57-58: Good updates to the return value!

The inclusion of isLoading in the return object and the null/undefined check for queryData are excellent improvements. They provide more information to the hook consumers and make the code more robust.

A minor optimization suggestion:

Consider using the nullish coalescing operator for a more concise expression:

-data: sort(toJS(queryData ? [...queryData] : [])),
+data: sort(toJS([...queryData ?? []])),

This achieves the same result but is slightly more readable.

packages/desktop-client/src/hooks/usePreviewTransactions.ts (1)

26-31: LGTM: Improved data handling and performance

The use of useCachedSchedules and usePrevious hooks, along with the early return when data is loading, significantly improves the hook's performance and data handling. This aligns well with React best practices.

Consider adding a loading state to the return value for better UX handling:

if (scheduleData.isLoading) {
  return { transactions: [], isLoading: true };
}

// ... rest of the hook logic

return { transactions: previewTransactions, isLoading: false };

This change would allow consumers of the hook to easily display loading indicators when necessary.

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

36-41: Improved hook usage and query efficiency.

The refactoring of the useSchedules hook usage is well done. Destructuring isLoading and schedules directly improves readability. The introduction of queryBuilder with useCallback is a good practice for memoization.

Consider renaming isLoading to isSchedulesLoading for consistency with its usage on line 43:

-  const { isLoading: isSchedulesLoading, schedules } = useSchedules({
+  const { isLoading: isSchedulesLoading, schedules } = useSchedules({

54-54: Improved null safety in render logic.

The use of optional chaining when accessing schedule properties is a good improvement for handling potential undefined values.

Consider adding a null check before formatting the date to avoid unnecessary function calls:

-              {format(schedule?.next_date || '', 'MMMM dd, yyyy')}
+              {schedule?.next_date ? format(schedule.next_date, 'MMMM dd, yyyy') : ''}

This ensures that format is only called when next_date is available.

Also applies to: 68-68

packages/desktop-client/src/components/schedules/index.tsx (1)

40-68: onAction function memoized and action handling consolidated.

The onAction function is now memoized using useCallback, which is consistent with the other functions in this component. The empty dependencies array is correct as the function doesn't rely on any external variables.

Consider extracting the logic for each action into separate functions for improved maintainability. This would make the code more modular and easier to test. For example:

const handlePostTransaction = useCallback(async (id: ScheduleEntity['id']) => {
  await send('schedule/post-transaction', { id });
}, []);

const handleSkip = useCallback(async (id: ScheduleEntity['id']) => {
  await send('schedule/skip-next-date', { id });
}, []);

// ... other action handlers ...

const onAction = useCallback(async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
  switch (name) {
    case 'post-transaction':
      return handlePostTransaction(id);
    case 'skip':
      return handleSkip(id);
    // ... other cases ...
    default:
      throw new Error(`Unknown action: ${name}`);
  }
}, [handlePostTransaction, handleSkip /*, other handlers */]);

This approach would make each action more isolated and easier to maintain or modify in the future.

packages/desktop-client/src/components/accounts/Reconcile.tsx (2)

28-28: Improved ReconcilingMessage component with dynamic sheet value naming

The changes in the ReconcilingMessage component effectively utilize the new accountId prop and simplify the useSheetValue hook usage:

  1. Dynamic sheet value naming using accountId increases flexibility.
  2. Simplified query filtering may improve performance.

These modifications align well with the PR objectives and the type changes.

For consistency, consider using a template literal for the name property:

name: `balance-query-${accountId}-cleared`,

This matches the type definition more closely and improves readability.

Also applies to: 35-38


Line range hint 1-24: Summary: Successful migration to new structure with improved flexibility

The changes in this file effectively:

  1. Enhance type definitions for better type safety.
  2. Improve component flexibility with the addition of accountId.
  3. Simplify query handling in the useSheetValue hook.

These modifications align well with the PR objectives of migrating to functional components and utilizing the new useTransactions hook. The code maintains good readability and follows React best practices.

Consider documenting the purpose of the accountId prop and its relationship to the balanceQuery in a comment above the ReconcilingMessageProps type. This would help future developers understand the component's design more quickly.

Also applies to: 25-38

packages/desktop-client/e2e/accounts.test.js (1)

114-114: Consistent use of explicit wait improves test reliability.

The addition of await accountPage.waitFor(); after account creation is a good practice, ensuring the page is fully loaded before proceeding with import tests. This change improves test reliability and is consistent with the earlier change.

For better maintainability, consider extracting this common wait pattern into a helper method in the AccountPage class, such as createAndWaitForAccount(). This would encapsulate the account creation and waiting logic, making it easier to reuse and modify in the future.

packages/desktop-client/e2e/page-models/account-page.js (1)

33-35: Approve the addition of the waitFor() method with a suggestion for error handling.

The new waitFor() method is a valuable addition to the AccountPage class. It enhances the reliability of e2e tests by ensuring the transaction table is ready before proceeding with interactions.

Consider adding a timeout and error handling to make the method more robust:

async waitFor(timeout = 30000) {
  try {
    await this.transactionTable.waitFor({ timeout });
  } catch (error) {
    throw new Error(`Transaction table not ready after ${timeout}ms: ${error.message}`);
  }
}

This change would allow customizable timeout and provide more informative error messages.

packages/desktop-client/src/components/transactions/TransactionList.jsx (1)

Line range hint 93-143: Optimization of transaction update functions looks good.

The changes to onAdd, onSave, onAddSplit, and onSplit functions appear to optimize the update process by reducing unnecessary refetches. This aligns well with the PR objectives of improving transaction handling efficiency.

The new behavior in onSave for updating sort_order when a transaction's date changes is a good addition to maintain proper sorting.

Consider extracting the date change logic in onSave into a separate function for better readability:

const handleDateChange = (updatedTransaction) => {
  updatedTransaction.sort_order = Date.now();
  return saveDiff({ updated: [updatedTransaction] }).then(onRefetch);
};

// In onSave:
if (dateChanged) {
  await handleDateChange(changes.diff.updated[0]);
} else {
  // ... existing else block
}
packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (3)

Line range hint 40-86: Enhance AccountCard accessibility

Consider adding more accessibility attributes to the AccountCard component to improve the user experience for screen reader users:

  1. Add an aria-label to the Button component that includes the account name and balance.
  2. Use aria-live="polite" on the status indicator (the colored dot) to announce status changes.

Example implementation:

<Button
  onPress={() => onSelect(account.id)}
  aria-label={`${account.name} account, balance ${props.formattedValue}`}
  // ... other props
>
  {/* ... existing content */}
  <View
    aria-live="polite"
    aria-label={`Account status: ${pending ? 'pending' : failed ? 'failed' : 'connected'}`}
    style={{
      // ... existing styles
    }}
  />
  {/* ... rest of the component */}
</Button>

These changes will provide more context to screen reader users about the account details and status.


Line range hint 89-99: Improve EmptyMessage component

The current EmptyMessage component provides basic information, but it could be more helpful to users. Consider adding:

  1. A clear call-to-action button to add an account.
  2. More specific instructions on how to add an account.

Example implementation:

function EmptyMessage({ onAddAccount }) {
  return (
    <View style={{ flex: 1, padding: 30 }}>
      <Text style={styles.text}>
        To get started with Actual, you need to add an account. You can:
        • Link an account to automatically download transactions
        • Manage an account locally yourself
      </Text>
      <Button onPress={onAddAccount} style={{ marginTop: 20 }}>
        Add Your First Account
      </Button>
    </View>
  );
}

This change would provide clearer guidance and a direct action for new users.


Line range hint 258-260: Add user feedback for sync action

The onSync function currently dispatches an action but doesn't provide any feedback to the user. Consider adding a visual indicator or notification to inform the user that a sync is in progress.

Example implementation:

const onSync = () => {
  dispatch(syncAndDownload());
  // Add a toast notification or update a loading state
  showToast('Syncing accounts...');
  // Or update a local state
  // setIsSyncing(true);
};

This change would improve the user experience by providing clear feedback on the sync action.

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

31-34: LGTM! Consider using a type alias for improved readability.

The changes to the accountFilter function (previously getAccountFilter) look good. The function now handles a wider range of account types, improving its flexibility while maintaining backwards compatibility.

Consider creating a type alias for the accountId parameter to improve code readability:

type AccountIdType = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized';

export function accountFilter(
  accountId?: AccountIdType,
  field = 'account',
) {
  // ... rest of the function
}

export function transactions(
  accountId?: AccountIdType,
) {
  // ... rest of the function
}

This change would make the function signatures more concise and easier to understand at a glance.

Also applies to: 70-72


83-85: LGTM! Consider updating the function signature for consistency.

The renaming of makeTransactionSearchQuery to transactionsSearch is a good improvement, making the function name more concise and clear.

For consistency with the other updated functions, consider updating the accountId parameter in the currentQuery to use the same type as accountFilter and transactions:

export function transactionsSearch(
  currentQuery: Query & { accountId?: AccountIdType },
  search: string,
  dateFormat: SyncedPrefs['dateFormat'],
) {
  // ... rest of the function
}

This change would ensure that all transaction-related functions use consistent types for account IDs.

packages/desktop-client/src/hooks/useSelected.tsx (2)

Line range hint 1-1: Consider removing the // @ts-strict-ignore comment

The // @ts-strict-ignore comment at the top of the file disables strict TypeScript checking. It's generally a good practice to enable strict checking to catch potential type-related issues early. Consider removing this comment and addressing any TypeScript errors that may arise.

Would you like assistance in removing the // @ts-strict-ignore comment and addressing any resulting TypeScript errors?


Line range hint 1-353: Consider performance optimizations for large lists

The current implementation of useSelected and related components is well-structured and functional. However, for large lists of items, there might be room for performance optimization. Consider the following suggestions:

  1. Memoize the items array passed to useSelected to prevent unnecessary re-renders.
  2. Use useMemo for expensive computations within the hook, such as filtering items in the select-all action.
  3. Consider using a virtual list component for rendering large lists of selectable items to improve rendering performance.

Would you like me to provide code examples for these optimizations?

packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (1)

42-42: LGTM! Consider adding JSDoc comment for clarity.

The addition of the optional isLoading prop is a good improvement, allowing the component to handle loading states. This aligns well with the PR's objective of enhancing component functionality.

Consider adding a JSDoc comment to explain the purpose of this prop:

/** Indicates whether the component is in a loading state */
isLoading?: boolean;
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)

253-253: Good use of readonly type for derived state!

The change from SchedulesTableItem[] to readonly SchedulesTableItem[] for the items variable is consistent with the earlier modification to the schedules prop type. It enforces immutability on the derived state, improving type safety and preventing accidental modifications within the component.

For consistency, consider updating the return type of the useMemo hook to explicitly declare it as readonly SchedulesTableItem[].

You can make this change by updating the line as follows:

const items = useMemo<readonly SchedulesTableItem[]>(() => {
packages/loot-core/src/client/query-helpers.test.ts (1)

313-313: LGTM: Updated property access and assertions for pagedQuery

The changes correctly update the property access on the paged object to align with the new structure returned by pagedQuery. The additional assertion for hasNext enhances the test coverage.

Consider adding a comment explaining the significance of the hasNext property to improve test readability.

Also applies to: 324-324, 335-335, 347-348

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

243-243: Optimized category parsing

The parseCategoryFields function has been updated to work with the new category structure. This change aligns well with the useCategories hook implementation.

Consider further optimizing this function by using find instead of forEach:

function parseCategoryFields(trans, categories) {
  return categories.find(category => 
    category.id === trans.category || category.name === trans.category
  )?.id || null;
}

This approach is more concise and stops iterating as soon as a match is found, potentially improving performance for large category lists.

Also applies to: 587-587


158-159: Streamlined category handling in ImportTransactionsModal

The changes in how categories are obtained and passed to child components are consistent with the new hook-based approach. This simplifies the component's structure and improves maintainability.

Consider adding error handling for the useCategories hook:

const { list: categories, error: categoriesError } = useCategories();

// Later in the component
if (categoriesError) {
  // Handle the error, e.g., display an error message
}

This addition would make the component more robust by gracefully handling potential errors in category fetching.

Also applies to: 789-789


243-243: Consistent category handling in getImportPreview and onImport

The modifications in getImportPreview and onImport functions align well with the new category management approach. The simplified category parsing maintains functionality while adapting to the new data structure.

Consider creating a category lookup map for potential performance improvement:

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

// Then in parseCategoryFields
function parseCategoryFields(trans, categoryMap) {
  return categoryMap.get(trans.category) || trans.category;
}

This approach could significantly speed up category lookups, especially for large category lists or when processing many transactions.

Also applies to: 587-587


Line range hint 1-1086: Overall improvement in category management and component structure

The changes in this file represent a significant improvement in how categories are managed within the ImportTransactionsModal component. Key improvements include:

  1. Centralized category management using the useCategories hook.
  2. Simplified props and internal logic for handling categories.
  3. Consistent updates throughout the file, maintaining overall functionality.

These changes enhance the component's maintainability and potentially its performance. The refactoring aligns well with React best practices and sets a good foundation for future improvements.

As the component grows in complexity, consider breaking it down into smaller, more focused components. This could further improve maintainability and make the code easier to test.

packages/loot-core/src/client/data-hooks/filters.ts (2)

Line range hint 8-16: Add type annotation to the 'rows' parameter in 'toJS' function

The toJS function currently lacks a type annotation for the rows parameter. Adding an explicit type enhances type safety and code readability.

Apply this diff to add the parameter type:

-function toJS(rows): TransactionFilterEntity[] {
+function toJS(rows: any[]): TransactionFilterEntity[] {

Replace any[] with the appropriate type if known.


29-35: Handle potential undefined 'name' properties during sorting

When sorting the filters, if any name property is undefined or null, it could cause a runtime error. Consider adding a check to ensure name is defined before calling trim().

Apply this diff to handle undefined name properties:

 return filters.slice().sort((a, b) =>
-  a.name
+  (a.name || '')
     .trim()
     .localeCompare((b.name || '').trim(), undefined, { ignorePunctuation: true }),
 );
packages/loot-core/src/client/query-hooks.ts (2)

28-29: Consider providing a more descriptive error message when query is null

Setting the error to new Error('Query is null') might not provide sufficient context. Consider enhancing the error message to explain why the query is null or provide guidance on how to resolve it.


43-44: Enhance error handling in the onError callback

Currently, onError: setError sets the error state with the error received from liveQuery. Consider adding logging or additional error processing to provide more context when debugging issues arising from the query.

packages/loot-core/src/shared/query.ts (1)

136-138: Consider preserving tableOptions in the reset method

Currently, the reset method creates a new Query with only the table property, which might unintentionally discard existing tableOptions. If tableOptions should persist after a reset, consider modifying the method to include them.

You might update the reset method as follows:

reset() {
-   return q(this.state.table);
+   return new Query({
+     table: this.state.table,
+     tableOptions: this.state.tableOptions,
+   });
}
packages/desktop-client/src/components/accounts/Balance.jsx (2)

20-21: Add an empty line between import groups for better readability.

According to the project's style guidelines, there should be at least one empty line between different groups of imports. This enhances code readability and organization.

Apply this diff to add an empty line between import groups:

 import { useSheetValue } from '../spreadsheet/useSheetValue';

+import { runQuery } from 'loot-core/client/query-helpers';

Note: After removing the unused runQuery import as suggested earlier, this change might not be necessary.

🧰 Tools
🪛 GitHub Check: lint

[warning] 20-20:
There should be at least one empty line between import groups


[warning] 21-21:
loot-core/client/query-helpers import should occur before import of loot-core/shared/transactions


[warning] 21-21:
'runQuery' is defined but never used. Allowed unused vars must match /^(_|React)/u


185-195: Consider adding parentheses for clarity in complex expressions.

In the CSS styling for the SvgArrowButtonRight1 component, adding parentheses around the ternary expressions can improve readability and prevent potential issues with operator precedence.

Apply this diff to add parentheses:

             opacity:
-              selectedItems.size > 0 || showExtraBalances ? 1 : 0,
+              (selectedItems.size > 0 || showExtraBalances) ? 1 : 0,
packages/loot-core/src/client/query-helpers.ts (1)

Line range hint 383-417: Potential for infinite recursion in _fetchNext method.

In the _fetchNext method, if this.inflightRequestId or previousData !== this.data is true, the method recursively calls itself without any delay. In situations where data keeps changing, this could potentially lead to a stack overflow or performance issues.

Consider adding a maximum retry limit or introducing a delay between retries to prevent potential infinite recursion.

packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (1)

Line range hint 166-166: Fix typo in comment

There's a typo in the comment: "defalut" should be "default".

Apply this diff to correct the typo:

-              // defalut to a non-zero value so the sign can be changed before the value
+              // default to a non-zero value so the sign can be changed before the value
packages/desktop-client/src/components/modals/EditRuleModal.jsx (1)

324-324: Improve user feedback when no schedules are found

Instead of displaying the id, consider showing a user-friendly message to enhance the user experience.

Apply this diff to update the message:

-if (schedules.length === 0) {
-  return <View style={{ flex: 1 }}>{id}</View>;
+if (schedules.length === 0) {
+  return <View style={{ flex: 1 }}>No schedules available.</View>;
}
packages/desktop-client/src/components/accounts/Header.tsx (1)

381-392: Enhance accessibility for the Reconcile button

The Reconcile button uses a View component with a title prop for the tooltip, which may not be accessible to all users. Consider adding an aria-label directly to the button or using a tooltip component that ensures better accessibility support.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 715bc00 and fddd46f.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
📒 Files selected for processing (43)
  • .eslintrc.js (1 hunks)
  • packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
  • packages/desktop-client/e2e/accounts.test.js (2 hunks)
  • packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
  • packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (1 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (2 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (1 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (2 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
  • packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
  • packages/loot-core/src/client/queries.ts (2 hunks)
  • packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
  • packages/loot-core/src/client/query-hooks.ts (1 hunks)
  • packages/loot-core/src/shared/query.ts (9 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useSchedules.ts
🧰 Additional context used
📓 Learnings (5)
.eslintrc.js (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: .eslintrc.js:164-169
Timestamp: 2024-10-18T15:33:24.453Z
Learning: When updating the `additionalHooks` pattern for `react-hooks/exhaustive-deps`, only `useQuery` should be added, not generalized patterns.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (4)
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
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.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
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.
Learnt from: MatissJanis
PR: actualbudget/actual#3570
File: packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx:83-90
Timestamp: 2024-10-08T15:46:15.739Z
Learning: In the `Transaction` component in `Transaction.tsx`, both `rawTransaction` and `transaction` should be included in the dependency arrays of `useMemo` hooks, even though `transaction` derives from `rawTransaction`.
Learnt from: MatissJanis
PR: actualbudget/actual#3570
File: packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx:83-90
Timestamp: 2024-10-05T10:58:13.598Z
Learning: In the `Transaction` component in `Transaction.tsx`, both `rawTransaction` and `transaction` should be included in the dependency arrays of `useMemo` hooks, even though `transaction` derives from `rawTransaction`.
packages/loot-core/src/client/data-hooks/dashboard.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/data-hooks/reports.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 GitHub Check: lint
packages/desktop-client/src/components/accounts/Balance.jsx

[warning] 1-1:
'useEffect' is defined but never used. Allowed unused vars must match /^(_|React)/u


[warning] 1-1:
'useState' is defined but never used. Allowed unused vars must match /^(_|React)/u


[warning] 20-20:
There should be at least one empty line between import groups


[warning] 21-21:
loot-core/client/query-helpers import should occur before import of loot-core/shared/transactions


[warning] 21-21:
'runQuery' is defined but never used. Allowed unused vars must match /^(_|React)/u

packages/desktop-client/src/hooks/usePreviewTransactions.ts

[warning] 19-19:
exported declaration 'usePreviewTransactions' not used within other modules

🪛 Biome
packages/loot-core/src/client/query-helpers.ts

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🔇 Additional comments (108)
packages/desktop-client/src/hooks/useNotes.ts (1)

3-3: LGTM: Import statement updated correctly.

The import statement has been updated to use the new useQuery hook from the centralized location. This change aligns with the PR objective and improves code organization.

packages/loot-core/src/client/data-hooks/dashboard.ts (4)

5-5: LGTM: Import statement for useQuery added.

The import statement for useQuery is correctly added and aligns with the new implementation.


8-10: LGTM: Successful migration from useLiveQuery to useQuery.

The implementation has been correctly updated to use useQuery instead of useLiveQuery. The destructuring now includes both data and isLoading, which aligns with the useQuery hook's return value. The type annotation and query function remain consistent, ensuring type safety and maintaining the existing query logic.


15-18: LGTM: Return statement and useMemo dependency array updated correctly.

The changes in the return object and useMemo dependency array are appropriate:

  1. isLoading is now directly used from the useQuery hook, which is more efficient.
  2. The fallback to an empty array for data is maintained, ensuring consistent behavior.
  3. The useMemo dependency array now correctly includes both isLoading and queryData, ensuring proper recalculation of the memoized value.

These changes align well with the new useQuery implementation and maintain the component's reactivity.


Line range hint 1-20: Overall: Excellent migration to useQuery with improved state handling.

The changes in this file successfully achieve the PR's objective of migrating to functional components and utilizing the new useQuery hook. Key improvements include:

  1. Proper implementation of useQuery, replacing useLiveQuery.
  2. Enhanced state management with direct access to isLoading.
  3. Improved reactivity in the useMemo hook with an updated dependency array.

These changes align well with the codebase's evolution towards more efficient data fetching and state management. The implementation is consistent with the known behavior of useLiveQuery as a wrapper around useQuery, now directly using useQuery for better control over the returned data and loading state.

packages/loot-core/src/client/data-hooks/widget.ts (3)

5-5: LGTM. Verify new hook functionality.

The change from useLiveQuery to useQuery aligns with the PR objectives. This shift in querying mechanism is likely part of a larger refactoring effort.

To ensure this change doesn't introduce any regressions, please verify that useQuery provides similar functionality to useLiveQuery in the context of this component.


7-7: Great improvement in type safety!

The updated function signature enhances type safety by ensuring that the id parameter matches the id type of the Widget generic type. This change will help catch potential type-related errors at compile-time, improving overall code reliability.


18-18: Excellent update to useMemo dependencies!

Including isLoading in the useMemo dependency array is a great improvement. This change ensures that the memoized object updates correctly when the loading state changes, not just when the data changes. It aligns with React's best practices for hook dependencies and improves the overall reliability of the useWidget hook.

packages/desktop-client/e2e/page-models/mobile-accounts-page.js (2)

7-7: LGTM: Good addition for improved element selection.

The new accountList property using getByLabel is a positive change. It enhances accessibility and maintainability by selecting elements based on their labels rather than implementation-specific attributes.


Line range hint 1-38: Overall, excellent improvements to the MobileAccountsPage model.

The changes in this file significantly enhance the robustness of the e2e tests for the mobile accounts page. The new accountList property and waitFor method work together to ensure proper synchronization and reliable element selection. These improvements align perfectly with the PR's objective of enhancing code structure and efficiency.

packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)

3-3: LGTM: Import statement updated correctly.

The import for useSchedules has been appropriately updated to use the centralized hook from the core library. This change improves code organization and promotes reusability across the application.


Line range hint 1-38: Overall, the changes look good and align with the PR objectives.

The modifications to ScheduleValue.tsx successfully migrate the component to use the new useSchedules hook from the core library. The changes improve error handling and align with best practices for React functional components. The suggestions provided for using the non-null assertion operator and adding a loading indicator are optional enhancements that could further improve the component.

packages/desktop-client/e2e/page-models/mobile-account-page.js (2)

18-20: LGTM! Great addition for test reliability.

The waitFor() method is a valuable addition that enhances the robustness of the tests. By ensuring the transactionList element is present before proceeding, it helps prevent race conditions and flaky tests.

This aligns well with similar changes in other files, promoting consistent synchronization across the application's test suite.


Line range hint 1-53: Overall, these changes enhance test reliability and align with PR objectives.

The additions to the MobileAccountPage class improve synchronization with UI state and provide useful functionality for managing the search feature. These changes contribute to the overall goal of improving transaction handling and test reliability across the application.

packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (3)

36-37: LGTM: Simplified ref update

Moving the latestOnChange ref update outside of useLayoutEffect is a good change. It ensures that the latest onChange callback is always available and simplifies the code.


38-39: Good addition: Efficient value tracking

The introduction of the latestValue ref is a good optimization. It allows for efficient comparison in the useLayoutEffect callback, potentially reducing unnecessary re-renders. The immediate update of latestValue.current ensures consistency with the current state.


55-55: Verify necessity of added dependencies

The expansion of the useLayoutEffect dependency array is good for ensuring the effect re-runs when relevant values change. However, please verify if all these new dependencies (spreadsheet, bindingObj.name, bindingObj.query) are necessary. Consider the following:

  1. Are all these values expected to change frequently?
  2. Could any of these cause unnecessary re-renders?
  3. Is spreadsheet a stable reference, or does it change on every render?

If any of these dependencies are stable or don't affect the effect's behavior, consider removing them to optimize performance.

To help verify the necessity of these dependencies, you can run the following script:

packages/loot-core/src/client/data-hooks/reports.ts (3)

39-42: Great implementation of useQuery!

The change from useLiveQuery to useQuery is well-implemented. The destructuring to get both data and isLoading is a good practice, allowing for more granular control over the component's rendering based on the loading state.


60-60: Correct update to useMemo dependencies.

The inclusion of both isLoading and queryData in the dependency array is correct and necessary. This ensures that the memoized value is recalculated when either the loading state or the query data changes, maintaining the accuracy of the hook's output.


8-8: LGTM: Import statement updated correctly.

The change from useLiveQuery to useQuery aligns with the PR objective of using the new hook structure.

Let's verify if this change is applied consistently throughout the file:

packages/desktop-client/e2e/accounts.mobile.test.js (4)

30-30: Improved test reliability with page load wait

The addition of await accountsPage.waitFor(); enhances the test's reliability by ensuring the accounts page is fully loaded before proceeding with assertions. This aligns with best practices in end-to-end testing and helps prevent flaky tests caused by timing issues.


41-42: Consistent improvement in test reliability and readability

The addition of await accountsPage.waitFor(); is consistent with the previous change, further enhancing test reliability. The empty line improves code readability by clearly separating the navigation step from the subsequent actions.


44-44: Critical wait added before account page interactions

The addition of await accountPage.waitFor(); is crucial here as it ensures the individual account page is fully loaded before the subsequent assertions and interactions. This significantly reduces the risk of flaky tests, especially given the multiple assertions that follow this wait statement.


57-59: Enhanced test coverage with search clear verification

Excellent addition of a new test scenario. The new lines verify that clearing the search functionality works correctly and that transactions are displayed after clearing. This improves the overall test coverage and ensures that the search feature behaves as expected in both directions (filtering and clearing).

The empty line also enhances readability by clearly separating this new scenario from the subsequent search test.

packages/desktop-client/src/hooks/usePreviewTransactions.ts (2)

9-12: LGTM: Improved type imports

The addition of the TransactionEntity type import and the reorganization of imports enhance type safety and code readability.


14-14: LGTM: Added usePrevious hook

The addition of the usePrevious hook import suggests an improvement in state management. This is a good practice for comparing current and previous states.

Please ensure that the usePrevious hook is correctly implemented and used within the component. You can verify its usage with the following script:

✅ Verification successful

The usePrevious hook is correctly implemented and properly utilized within the usePreviewTransactions component.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation and usage of usePrevious hook

# Test: Check for the implementation of usePrevious hook
echo "Checking for usePrevious hook implementation:"
rg -p "export function usePrevious" packages/desktop-client/src/hooks/

# Test: Verify usage of usePrevious in usePreviewTransactions
echo "Verifying usage of usePrevious in usePreviewTransactions:"
rg -p "usePrevious" packages/desktop-client/src/hooks/usePreviewTransactions.ts

Length of output: 606

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

43-46: Appropriate loading state handling.

The addition of the loading state check is a good practice. Returning null while the schedules are loading prevents rendering incomplete data and potential errors.


47-48: Improved schedule access, but consider empty array case.

The use of optional chaining when accessing the first schedule is good practice. However, consider handling the case where schedules might be an empty array.

Consider adding a check for an empty array:

-  const schedule = schedules?.[0];
+  const schedule = schedules && schedules.length > 0 ? schedules[0] : undefined;
+
+  if (!schedule) {
+    // Handle case where no schedule is found
+    return null; // or some appropriate UI
+  }

This ensures that you're explicitly handling cases where no schedules are returned.

packages/desktop-client/src/components/schedules/index.tsx (4)

1-7: Imports updated to support functional component and hooks usage.

The changes in imports reflect the migration to a functional component and the adoption of hooks. The introduction of useDispatch and removal of useActions indicates a shift towards more direct Redux state management, which is a good practice for better control and transparency in state updates.


22-24: Redux dispatch and local state management implemented correctly.

The use of useDispatch aligns with modern Redux patterns, providing direct access to the dispatch function. The local filter state is managed using useState, which is appropriate for this use case.


25-38: Functions optimized with useCallback and consistent modal dispatch.

The onEdit, onAdd, and onDiscover functions are now memoized using useCallback, which is a good optimization for preventing unnecessary re-renders. The consistent use of dispatch(pushModal(...)) across these functions improves code readability and maintainability. The dependency arrays are correctly specified, ensuring that the functions are only recreated if dispatch changes.


70-73: Improved data fetching and loading state handling.

The use of the useSchedules hook simplifies data fetching and provides a loading state. The addition of the loading check before rendering the component content is a good practice. It prevents potential errors from accessing undefined data and improves the user experience by not showing incomplete content.

packages/desktop-client/src/components/schedules/ScheduleLink.tsx (1)

42-42: Approve renaming of transform to queryBuilder

The change from transform to queryBuilder in the useSchedules hook improves code clarity by more accurately describing the function's purpose. This aligns with the broader refactoring efforts mentioned in the PR objectives.

To ensure consistency across the codebase, please run the following script:

This script will help identify any inconsistencies in the usage of useSchedules across the codebase.

✅ Verification successful

Verified renaming of transform to queryBuilder

All instances of useSchedules have been successfully updated to use queryBuilder. The refactoring is consistent across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining instances of 'transform' in useSchedules calls
# and verify that all instances have been updated to 'queryBuilder'.

# Search for useSchedules calls
echo "Searching for useSchedules calls:"
rg "useSchedules\s*\(\s*\{" -g "*.{js,jsx,ts,tsx}"

# Search for any remaining 'transform' properties in useSchedules calls
echo "Checking for any remaining 'transform' properties in useSchedules calls:"
rg "useSchedules\s*\(\s*\{[^}]*transform\s*:" -g "*.{js,jsx,ts,tsx}"

# Search for 'queryBuilder' properties in useSchedules calls
echo "Verifying 'queryBuilder' usage in useSchedules calls:"
rg "useSchedules\s*\(\s*\{[^}]*queryBuilder\s*:" -g "*.{js,jsx,ts,tsx}"

Length of output: 1085


Script:

#!/bin/bash
# Description: Verify that all useSchedules calls use 'queryBuilder' instead of 'transform'.
# This script checks for both explicit ('queryBuilder:') and shorthand ('queryBuilder') property usages.

# Search for useSchedules calls
echo "Searching for useSchedules calls:"
rg "useSchedules\s*\(\s*\{" -g "*.{js,jsx,ts,tsx}"

# Ensure no 'transform' properties remain in useSchedules calls
echo "Checking for any remaining 'transform' properties in useSchedules calls:"
rg "useSchedules\s*\(\s*\{[^}]*transform\s*:" -g "*.{js,jsx,ts,tsx}"

# Verify 'queryBuilder' usage in useSchedules calls, accounting for both explicit and shorthand syntax
echo "Verifying 'queryBuilder' usage in useSchedules calls (both explicit and shorthand):"
rg "useSchedules\s*\(\s*\{[^}]*(queryBuilder\s*:|queryBuilder\b)" -g "*.{js,jsx,ts,tsx}"

Length of output: 1264


Script:

#!/bin/bash
# Description: Identify 'useSchedules' calls that do not include the 'queryBuilder' property.

# Search for 'useSchedules' calls without 'queryBuilder'
echo "Searching for useSchedules calls missing 'queryBuilder':"
rg "useSchedules\s*\(\s*\{(?![^}]*\bqueryBuilder\b)" -g "*.{js,jsx,ts,tsx}"

Length of output: 479


Script:

#!/bin/bash
# Description: Identify 'useSchedules' calls that do not include the 'queryBuilder' property.

# Search for 'useSchedules' calls
echo "Searching for useSchedules calls without 'queryBuilder':"
rg "useSchedules\s*\(\s*\{[^}]*\}" -g "*.{js,jsx,ts,tsx}" | grep -L "queryBuilder"

Length of output: 211

packages/desktop-client/src/components/accounts/Reconcile.tsx (1)

20-21: Improved type definition for ReconcilingMessageProps

The changes to the ReconcilingMessageProps type enhance flexibility and simplify the interface:

  1. Adding accountId allows for more versatile account identification.
  2. Simplifying balanceQuery to Query type streamlines the prop structure.

These modifications align well with the PR objectives and should improve code maintainability.

packages/desktop-client/e2e/accounts.test.js (2)

65-66: Improved test reliability with explicit wait.

The addition of await accountPage.waitFor(); is a good practice. It ensures that the account page is fully loaded before proceeding with the test, which can prevent potential race conditions and improve test reliability.


Line range hint 1-165: Overall improvement in test reliability.

The changes in this file consistently improve test reliability by adding explicit waits after navigation or account creation. This aligns well with the PR objectives of enhancing test robustness. The overall structure and logic of the tests remain sound, with these additions addressing potential timing issues.

Consider applying similar wait patterns to other test files if they haven't been addressed already, to ensure consistent reliability across the entire test suite.

packages/desktop-client/src/components/transactions/TransactionList.jsx (3)

60-60: LGTM: Addition of isLoading prop enhances loading state management.

The new isLoading prop with a default value of false is a good addition. It aligns with the overall improvements in loading state management across components, as mentioned in the PR summary.


201-201: LGTM: Propagation of isLoading prop to TransactionTable.

Passing the isLoading prop to the TransactionTable component is a good practice. It allows for consistent loading state management throughout the transaction list UI hierarchy.


Line range hint 1-241: Overall assessment: Changes align well with PR objectives.

The modifications to TransactionList.jsx successfully improve transaction handling efficiency and enhance loading state management. The changes are consistent with the PR objectives and the AI-generated summary. The code is well-structured, and the new features have been integrated smoothly.

packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1)

187-187: Excellent accessibility improvement!

Adding the aria-label="Account list" to the View component is a great enhancement for screen reader users. This label provides clear context about the content within this section, making the application more accessible.

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

70-72: LGTM! Consistent and clear changes.

The renaming of makeTransactionsQuery to transactions and the updated signature are good improvements. These changes maintain consistency with the accountFilter function while preserving the existing functionality.

Also applies to: 75-79

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

Line range hint 1-368: Summary: Minor refactoring with no functional changes.

The changes in this file are part of a larger refactoring effort to improve code organization and consistency. The useSchedules hook import and usage have been updated without introducing any functional changes to the ManageRules component. The rest of the component remains unchanged, maintaining its original functionality.


12-12: LGTM! Verify usage across the codebase.

The import statement for useSchedules has been updated to use a centralized location in the loot-core package. This change is likely part of a larger refactoring effort to improve code organization.

To ensure consistency, please verify that all other components using the useSchedules hook have been updated similarly:

#!/bin/bash
# Search for any remaining imports of useSchedules from the old location
rg "from '../hooks/useSchedules'" packages/desktop-client/src

116-116: LGTM! Verify useSchedules hook implementation.

The destructuring of the useSchedules hook's return value has been simplified, directly assigning schedules instead of using the data property. This change maintains backwards compatibility and doesn't affect the component's functionality.

To ensure consistency, please verify the implementation of the useSchedules hook:

✅ Verification successful

Destructuring of useSchedules hook verified and correct.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Display the content of the useSchedules hook
cat packages/desktop-client/src/hooks/useSchedules.ts || cat packages/desktop-client/src/hooks/useSchedules.js || cat loot-core/client/data-hooks/schedules.ts || cat loot-core/client/data-hooks/schedules.js

Length of output: 432


Script:

#!/bin/bash
# Search for the definition and usage of useSchedules hook
rg 'useSchedules' --glob "*.ts" --glob "*.tsx" --glob "*.js" --glob "*.jsx"

Length of output: 1780


Script:

#!/bin/bash
# Display the content of the useSchedules hook
cat packages/loot-core/src/client/data-hooks/schedules.tsx

Length of output: 4688

packages/desktop-client/src/hooks/useSelected.tsx (2)

Line range hint 1-353: Overall, great improvements to immutability and solid implementation

The changes to introduce readonly arrays enhance the robustness of the code by preventing accidental mutations. The overall implementation of the selection logic is solid and well-structured.

Consider the suggestions for removing the // @ts-strict-ignore comment and exploring performance optimizations for large lists to further improve the code quality and efficiency.

Great work on maintaining and improving this complex piece of functionality!


55-56: Excellent use of readonly arrays to promote immutability!

The changes to make items and initialSelectedIds readonly in both the useSelected hook and SelectedProviderWithItems component are a great improvement. This prevents accidental mutations of the input data, which can lead to more predictable and easier-to-debug code.

To ensure that these changes don't cause any issues elsewhere in the codebase, let's verify the usage of useSelected and SelectedProviderWithItems:

This script will help us identify any potential issues with the usage of these functions in the codebase.

Also applies to: 312-313

packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3)

59-59: LGTM! Proper implementation of the new prop.

The addition of the isLoading prop with a default value of false is correctly implemented. This approach maintains backward compatibility and follows React best practices for handling optional props.


Line range hint 1-339: Overall assessment: Well-implemented feature addition.

The changes to add the isLoading prop to the SelectedTransactionsButton component are well-implemented and consistent. They enhance the component's functionality by allowing it to handle loading states, which aligns with the PR objectives. The implementation maintains backward compatibility and follows React best practices.

Key points:

  1. The isLoading prop is properly typed and optional.
  2. A default value is provided to ensure backward compatibility.
  3. The prop is correctly passed to the child SelectedItemsButton component.

These changes contribute to improving the overall user experience and component flexibility.


211-211: LGTM! Verify SelectedItemsButton implementation.

The isLoading prop is correctly passed to the SelectedItemsButton component, allowing it to reflect the loading state. This change enhances the component's functionality and improves user experience.

To ensure full compatibility, let's verify the implementation of SelectedItemsButton:

✅ Verification successful

Verified: The isLoading prop is correctly implemented in the SelectedTransactionsButton component, ensuring the loading state is appropriately handled.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that SelectedItemsButton accepts and uses the isLoading prop
rg -A 5 'SelectedItemsButton.*isLoading' packages/desktop-client/src/components/table.tsx

Length of output: 91


Script:

#!/bin/bash
# Description: Verify that SelectedTransactionsButton accepts and uses the isLoading prop
rg 'isLoading\s*=' packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx

Length of output: 152

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)

33-33: Excellent use of readonly type for immutability!

The change from ScheduleEntity[] to readonly ScheduleEntity[] for the schedules prop is a great improvement. This enforces immutability, aligning with React's best practices and preventing accidental modifications to the prop within the component. It enhances type safety and can help prevent bugs related to unintended mutations.


Line range hint 1-391: Overall, excellent improvements to type safety!

The changes in this file focus on enhancing type safety and enforcing immutability, which are great improvements. By making the schedules prop and items derived state readonly, you've reduced the risk of accidental mutations and aligned the code with React best practices. These modifications contribute to a more robust and maintainable codebase without altering the component's core functionality.

Keep up the good work in leveraging TypeScript's features to improve code quality!

packages/loot-core/src/client/query-helpers.test.ts (11)

140-140: LGTM: Updated function call to use object parameter

The doQuery function call has been correctly updated to use an object parameter, which aligns with the new function signatures for liveQuery and pagedQuery. This change improves code readability and allows for easier addition of parameters in the future.


156-159: LGTM: Correctly updated function call with separated callback and options

The doQuery function call has been properly updated to use an object parameter, with onData callback and options correctly separated as properties. This change aligns with the new function signatures and improves code clarity.


174-177: LGTM: Consistent update of function call

The doQuery function call has been consistently updated to use an object parameter, maintaining the pattern established in previous changes. This ensures uniformity across the test file.


200-202: LGTM: Updated function call while preserving test logic

The doQuery function call has been correctly updated to use an object parameter, while still assigning the return value to lq. This change maintains the existing test logic while adapting to the new function signature.


230-233: LGTM: Consistent update of function call

The doQuery function call has been consistently updated to use an object parameter, maintaining the pattern established in previous changes. This ensures uniformity across the test file.


254-257: LGTM: Consistent update of function call

The doQuery function call has been consistently updated to use an object parameter, maintaining the pattern established in previous changes. This ensures uniformity across the test file.


279-281: LGTM: Updated function call while preserving test logic

The doQuery function call has been correctly updated to use an object parameter, while still assigning the return value to lq. This change maintains the existing test logic, including the subsequent unsubscribe operation, while adapting to the new function signature.


300-303: LGTM: Correctly updated pagedQuery call with multiple callbacks

The pagedQuery function call has been properly updated to use an object parameter, with both onData and onPageData callbacks correctly included as separate properties. This change aligns with the new function signature and improves code clarity.


361-363: LGTM: Correctly updated pagedQuery call with callback and options

The pagedQuery function call has been properly updated to use an object parameter, with the onData callback and options correctly separated as properties. This change aligns with the new function signature and improves code clarity.


378-380: LGTM: Consistent updates to pagedQuery calls across multiple test cases

The pagedQuery function calls have been consistently updated across multiple test cases to use object parameters. These changes correctly incorporate various combinations of callbacks and options, maintaining the existing test logic while adapting to the new function signature. This consistency ensures that all test cases are properly aligned with the updated pagedQuery implementation.

Also applies to: 404-407, 440-443, 484-487


Line range hint 1-524: Overall assessment: Successful adaptation of tests to new function signatures

The changes in this file consistently update all liveQuery and pagedQuery function calls to use object parameters, aligning with the new function signatures. These modifications:

  1. Improve code readability and maintainability.
  2. Preserve the original test coverage and intent.
  3. Provide a more flexible structure for potential future parameter additions.

The updates have been applied uniformly across all test cases, ensuring that the entire test suite remains consistent with the new implementation of the query helper functions.

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

8-8: Improved category management with useCategories hook

The addition of the useCategories hook and the modification of the options destructuring represent a positive change in how categories are managed. This approach:

  1. Centralizes category data fetching.
  2. Potentially improves performance by avoiding unnecessary prop drilling.
  3. Enhances maintainability by separating concerns.

Also applies to: 158-159

packages/loot-core/src/client/data-hooks/filters.ts (1)

37-37: Verify dependencies in 'useMemo' for consistent memoization

Ensure that the dependencies [data, sort] in useMemo are sufficient and that data does not change on every render due to reference inequality. If data is a new array instance on each render, consider stabilizing it to prevent unnecessary recalculations.

Run the following script to check if data maintains reference equality across renders:

✅ Verification successful

Dependencies in useMemo are correctly specified and data maintains reference equality across renders.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for multiple instances where 'data' may be recreated.

# Test: Search for all uses of 'useQuery' that might cause 'data' to change unnecessarily.
rg "useQuery<.*>\(\s*\(\)\s*=>\s*q\("

# Expected: Instances where 'data' is stable and memoized properly.

Length of output: 39


Script:

#!/bin/bash
# Description: Check for instances where 'data' is recreated or modified and verify 'useMemo' dependencies.

# Search for all instances where 'data' is reassigned or recreated
rg 'data\s*=' packages/loot-core/src/client/data-hooks/filters.ts

# Search for 'useMemo' usage to verify dependencies
rg 'useMemo\s*\(\s*\(\s*\)\s*=>\s*sort\s*\(.*data.*\),\s*\[data,\s*sort\]\s*\)' packages/loot-core/src/client/data-hooks/filters.ts

Length of output: 274

packages/loot-core/src/client/query-hooks.ts (2)

7-11: Good use of a dedicated type for the query result

Defining UseQueryResult<Response> improves type safety and enhances code readability by clearly specifying the structure of the returned object from useQuery.


14-16: Allowing makeQuery to return null increases flexibility

Updating the makeQuery function to return Query | null allows for optional queries, which can be useful in conditional scenarios.

packages/loot-core/src/shared/query.ts (13)

1-5: Definition of ObjectExpression is appropriate

The recursive definition of ObjectExpression allows for nested expressions and provides flexibility for complex query expressions.


8-13: Updates to QueryState enhance flexibility and type safety

Adding table and tableOptions to QueryState, along with updating the types of expression arrays, improves the overall flexibility and type safety of the query system.


25-27: Constructor change ensures the table property is required

By using WithRequired<Partial<QueryState>, 'table'>, the constructor now enforces that a table is provided when creating a new Query instance, which is essential for query operations.


42-44: filter method correctly appends new expressions

The filter method properly adds new filter expressions to the existing array without modifying the original Query instance, maintaining immutability.


67-74: select method updates improve usability

Expanding the select method to accept various input types, including ObjectExpression, string, and '*', enhances its usability and aligns it with typical query usage patterns.


84-87: calculate method correctly uses select for computations

The calculate method leverages the select method to set up calculations, setting the calculation flag to distinguish it from regular selections.


Line range hint 90-95: groupBy method now accepts multiple expression types

Allowing groupBy to accept ObjectExpression, string, or arrays of these types provides greater flexibility in defining groupings within queries.


Line range hint 101-106: orderBy method enhancements increase ordering options

By accepting both ObjectExpression and string types, orderBy now supports more complex ordering scenarios, improving query customization.


112-114: limit method enforces numeric input

Specifying the num parameter as a number type in the limit method enhances type safety and prevents potential runtime errors.


116-118: offset method correctly specifies number type

Defining the num parameter as a number in the offset method ensures clarity and type safety in query pagination.


132-134: options method properly updates tableOptions

Accepting a Record<string, unknown> type for opts allows the options method to flexibly handle various table options.


145-148: getPrimaryOrderBy function now aligns with updated types

Updating the function to accept a Query instance and an ObjectExpression for defaultOrderBy improves consistency with the Query class enhancements.


166-168: q function enforces the table type from QueryState

By typing the table parameter as QueryState['table'], the q function ensures compatibility with the Query constructor and maintains type safety.

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (7)

30-35: Efficient use of useCallback for memoizing baseTransactionsQuery

The baseTransactionsQuery function is correctly memoized with useCallback, which optimizes performance by avoiding unnecessary re-creations when category or month change.


39-41: Proper initialization of transactionsQuery state

Initializing transactionsQuery with the result of baseTransactionsQuery() ensures that the initial query aligns with the current category and month.


42-49: Appropriate use of useTransactions hook

Utilizing the useTransactions hook with the custom transactionsQuery simplifies transaction management and improves code readability.


54-69: Correct handling of synchronization events in useEffect

The useEffect hook effectively listens for 'sync-event' and reloads transactions and payees as needed, ensuring the data remains up-to-date.


71-77: Effective implementation of search with debounced callback

The search functionality is efficiently managed using useTransactionsSearch and useDebounceCallback, which enhances performance by reducing unnecessary updates.


78-85: Proper navigation logic in onOpenTransaction

The onOpenTransaction function correctly handles navigation by checking for preview transactions and using navigate to open transactions when appropriate.


118-119: Passing correct handlers to TransactionListWithBalances

The onLoadMore and onOpenTransaction handlers are correctly passed to the component, ensuring proper functionality for loading more transactions and opening transactions.

packages/loot-core/src/client/data-hooks/schedules.tsx (1)

7-9: Correct addition of necessary React imports

The added imports for useRef, useMemo, and type PropsWithChildren are appropriate and necessary for the updated code.

packages/desktop-client/src/components/accounts/Balance.jsx (1)

159-166: Ensure 'balanceQuery' is defined before creating 'balanceBinding'.

In the useMemo hook, balanceBinding depends on balanceQuery. If balanceQuery is null or undefined, balanceBinding will have an undefined query, which might cause issues in the CellValue component.

Please confirm that balanceQuery is always defined before rendering the Balances component. If there's a possibility of it being undefined, consider adding a condition or providing a default value.

packages/loot-core/src/shared/transactions.ts (1)

93-96: Enhance immutability by adding readonly to parameters

Adding readonly to the transactions parameters in these functions enhances immutability and prevents unintended mutations of the input arrays. This is a good practice that promotes safer and more predictable code.

Also applies to: 110-113, 123-125, 139-139, 229-231, 248-250, 281-283, 306-308, 334-335, 355-357

packages/loot-core/src/client/query-helpers.ts (5)

10-22: Improved type safety in liveQuery function.

The use of generics and structured options enhances type safety and clarity in the liveQuery function.


25-45: Enhanced pagedQuery function with better options handling.

Applying generics and detailed option parameters improves the function's flexibility and usability.


48-57: Addition of well-defined types improves code maintainability.

Defining Data, Listener, LiveQueryOptions, and PagedQueryOptions provides better type definitions, enhancing readability and maintainability.


60-238: Refactored LiveQuery class enhances flexibility and robustness.

The use of generics and refactored methods improves the class's flexibility and robustness.

🧰 Tools
🪛 Biome

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)


Line range hint 247-425: Improved pagination handling in PagedQuery class.

Updates to the PagedQuery class, including the use of generics and better asynchronous handling, enhance the pagination functionality and code clarity.

🧰 Tools
🪛 Biome

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

.eslintrc.js (2)

164-169: Approved: Addition of 'useQuery' to 'additionalHooks' in 'react-hooks/exhaustive-deps'

This change ensures that the exhaustive-deps rule applies to useQuery, which is appropriate and aligns with previous decisions to include only useQuery in additionalHooks.


167-167: ⚠️ Potential issue

Consider refining the regex pattern to match 'useQuery' exactly

The current regex pattern (useQuery) might unintentionally match hook names that contain 'useQuery' as a substring (e.g., useQueryData). To ensure only useQuery is matched, consider changing the regex to ^useQuery$.

Apply this diff to refine the regex pattern:

           {
-            additionalHooks: '(useQuery)',
+            additionalHooks: '^useQuery$',
           },
⛔ Skipped due to learnings
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: .eslintrc.js:164-169
Timestamp: 2024-10-18T15:33:24.453Z
Learning: When updating the `additionalHooks` pattern for `react-hooks/exhaustive-deps`, only `useQuery` should be added, not generalized patterns.
packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (1)

301-304: Correct usage of liveQuery with onData callback

The updated liveQuery invocation with the onData property aligns with the new API usage and ensures that transaction data is dispatched appropriately.

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

842-843: Verify that ButtonWithLoading disables interaction when isLoading is true

Ensure that when isLoading is true, the ButtonWithLoading component prevents user interactions to avoid duplicate submissions or unintended behaviors.

Run the following script to verify how ButtonWithLoading handles the isLoading prop:

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

312-317: Correctly updated useSchedules hook usage

The useSchedules hook is now appropriately destructured to extract schedules, statuses (renamed to scheduleStatuses), and isLoading (renamed to isSchedulesLoading). The usage of useCallback for queryBuilder optimizes performance by preventing unnecessary recomputations when id doesn't change.


320-320: Appropriate handling of loading state

Returning null when isSchedulesLoading is true ensures that the component doesn't render incomplete data while schedules are being fetched.


328-329: Safe access to schedule and status

After verifying that schedules is not empty, destructuring the first schedule and retrieving its status from scheduleStatuses is correctly implemented.

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)

769-776: Correct handling of loading state in PayeeIcons component

The addition of the loading check ensures that the component does not render schedule-related icons until schedules are loaded, preventing potential errors.


Line range hint 1765-1775: 'isLoading' prop correctly integrated into TransactionTableInner

Including the isLoading prop in TransactionTableInner ensures that the loading state is properly managed and passed down to child components.


Line range hint 1994-2005: Passing 'isLoading' prop to Table component

The isLoading prop is correctly passed to the Table component, allowing it to display a loading indicator when transactions are being fetched.

packages/desktop-client/src/components/accounts/Header.tsx (4)

65-65: Ensure transactions being readonly does not cause issues

The transactions prop is now typed as readonly TransactionEntity[]. Please verify that no mutations are performed on this array within the component or its children, as this could lead to runtime errors or unexpected behavior.


296-296: Confirm that Balances component functions correctly without isLoading prop

The isLoading prop passed to the Balances component has been commented out. If Balances relies on this prop for displaying loading states, ensure that its removal does not affect the user experience.


199-199: Check for potential issues with local search state

The search state is now managed locally in the AccountHeader component, and the search prop has been commented out. Ensure that this change does not break any parent-child synchronization or shared state that relies on the search prop. Also, verify that any components or logic that depend on the search prop are updated accordingly.

Also applies to: 355-358, 155-155


450-450: Verify the removal of canShowBalances condition in AccountMenu

The canShowBalances prop and its associated logic have been commented out, and the "Toggle running balance" menu item is now always displayed. Previously, the display of this menu item was conditional. Confirm that showing this menu item in all cases is the intended behavior and does not lead to confusion or errors when the running balance cannot be displayed.

Also applies to: 699-699, 722-737

Comment on lines 33 to 66
if (scheduleData && scheduleData !== previousScheduleData) {
// Kick off an async rules application
const schedules = scheduleData.schedules.filter(s =>
isForPreview(s, scheduleData.statuses),
);

const baseTrans = schedules.map(schedule => ({
id: 'preview/' + schedule.id,
payee: schedule._payee,
account: schedule._account,
amount: schedule._amount,
date: schedule.next_date,
schedule: schedule.id,
}));
const baseTrans = schedules.map(schedule => ({
id: 'preview/' + schedule.id,
payee: schedule._payee,
account: schedule._account,
amount: schedule._amount,
date: schedule.next_date,
schedule: schedule.id,
}));

Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
).then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
).then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
}

return previewTransactions;
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

LGTM with suggestions: Improved efficiency and data structure

The changes to the main hook logic, particularly the check for updated scheduleData, improve the hook's efficiency. The updated transaction structure provides more comprehensive information, which is beneficial.

However, there's room for improvement in error handling:

Consider adding error handling to the Promise.all call:

Promise.all(
  baseTrans.map(transaction => send('rules-run', { transaction })),
)
  .then(newTrans => {
    // ... existing logic ...
  })
  .catch(error => {
    console.error('Error applying rules:', error);
    // Consider setting an error state or calling an error callback
  });

This addition will prevent unhandled promise rejections and provide better debugging information if an error occurs during rule application.

Comment on lines 16 to 19
/**
* @deprecated Please use `usePreviewTransactions` hook from `loot-core/client/data-hooks/transactions` instead.
*/
export function usePreviewTransactions(
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Deprecated usePreviewTransactions hook is still in use

The usePreviewTransactions hook is still being used in the following files:

  • packages/desktop-client/src/components/accounts/Account.tsx
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx

Please migrate these usages to the new hook from loot-core/client/data-hooks/transactions before removing the deprecated hook.

🔗 Analysis chain

Verify usage of deprecated hook

The usePreviewTransactions hook is marked as deprecated, and static analysis suggests it might not be in use.

Please verify if this hook is still needed:

  1. If it's no longer used, consider removing it entirely.
  2. If it's still in use, ensure that all occurrences are migrated to the new hook mentioned in the deprecation comment.

You can use the following script to check for any remaining usage:

Based on the results, decide whether to remove this hook or update the deprecation timeline.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usage of deprecated usePreviewTransactions hook

echo "Searching for usePreviewTransactions usage:"
rg -p "usePreviewTransactions" --type ts --type tsx --glob '!packages/desktop-client/src/hooks/usePreviewTransactions.ts'

echo "Searching for new hook usage:"
rg -p "usePreviewTransactions" packages/loot-core/src/client/data-hooks/transactions.ts

Length of output: 479


Script:

#!/bin/bash
# Description: Corrected script to check for usage of deprecated usePreviewTransactions hook

echo "Searching for usePreviewTransactions usage:"
rg -e "usePreviewTransactions" --type ts --type tsx --glob '!packages/desktop-client/src/hooks/usePreviewTransactions.ts'

echo "Searching for new hook usage:"
rg -e "usePreviewTransactions" packages/loot-core/src/client/data-hooks/transactions.ts

Length of output: 476


Script:

#!/bin/bash
# Description: Corrected script to check for usage of deprecated usePreviewTransactions hook

echo "Searching for usePreviewTransactions usage in .ts and .tsx files:"
rg -e "usePreviewTransactions" --glob "*.ts" --glob "*.tsx" --glob '!packages/desktop-client/src/hooks/usePreviewTransactions.ts'

echo "Searching for new hook usage:"
rg -e "usePreviewTransactions" packages/loot-core/src/client/data-hooks/transactions.ts

Length of output: 1056

🧰 Tools
🪛 GitHub Check: lint

[warning] 19-19:
exported declaration 'usePreviewTransactions' not used within other modules

Comment on lines 29 to 35
const sort = useCallback((filters: TransactionFilterEntity[]) => {
return filters.toSorted((a, b) =>
a.name
.trim()
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }),
);
}
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace 'toSorted' with a more widely supported sorting method

The Array.prototype.toSorted method is a recent addition and may not be supported in all environments, potentially leading to compatibility issues. Consider using slice().sort() or spreading the array before sorting to ensure broader compatibility.

Apply this diff to replace toSorted:

-return filters.toSorted((a, b) =>
+return filters.slice().sort((a, b) =>

Or using the spread operator:

-return filters.toSorted((a, b) =>
+return [...filters].sort((a, b) =>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sort = useCallback((filters: TransactionFilterEntity[]) => {
return filters.toSorted((a, b) =>
a.name
.trim()
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }),
);
}
}, []);
const sort = useCallback((filters: TransactionFilterEntity[]) => {
return filters.slice().sort((a, b) =>
a.name
.trim()
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }),
);
}, []);

Comment on lines 35 to 38
let isUnmounted = false;
let live: null | LiveQuery<Response> = liveQuery<Response>(query, {
onData: data => {
if (!isUnmounted) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use useRef for isUnmounted flag to prevent stale closure issues

Using a local variable isUnmounted inside useEffect can lead to stale closures in certain scenarios. Consider using useRef to ensure the flag remains consistent across renders.

Apply this diff to utilize useRef:

+ const isUnmounted = useRef(false);

  useEffect(() => {
-   let isUnmounted = false;
    // ...
    liveQuery<Response>(query, {
      onData: data => {
-       if (!isUnmounted) {
+       if (!isUnmounted.current) {
          setData(data);
          setIsLoading(false);
        }
      },
    });

    return () => {
-     isUnmounted = true;
+     isUnmounted.current = true;
      live?.unsubscribe();
      live = null;
    };
  }, [query]);

Committable suggestion was skipped due to low confidence.

Comment on lines 49 to 57
unfilter(exprs?: Array<keyof ObjectExpression>) {
// Remove all filters if no arguments are passed
if (!exprs) {
return new Query({
...this.state,
filterExpressions: [],
});
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential issue in unfilter when handling expressions with multiple keys

The unfilter method assumes each expression in filterExpressions has only one key by using Object.keys(expr)[0]. If a filter expression contains multiple keys, it may not be correctly removed, leading to unexpected behavior.

Consider modifying the unfilter method to handle expressions with multiple keys. One possible solution is to compare the entire keys array or use a more robust matching mechanism.

unfilter(exprs?: Array<keyof ObjectExpression>) {
  // Remove all filters if no arguments are passed
  if (!exprs) {
    return new Query({
      ...this.state,
      filterExpressions: [],
    });
  }

  const exprSet = new Set(exprs);
  return new Query({
    ...this.state,
    filterExpressions: this.state.filterExpressions.filter(expr => {
-     return !exprSet.has(Object.keys(expr)[0]);
+     return !Object.keys(expr).some(key => exprSet.has(key));
    }),
  });
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
unfilter(exprs?: Array<keyof ObjectExpression>) {
// Remove all filters if no arguments are passed
if (!exprs) {
return new Query({
...this.state,
filterExpressions: [],
});
}
unfilter(exprs?: Array<keyof ObjectExpression>) {
// Remove all filters if no arguments are passed
if (!exprs) {
return new Query({
...this.state,
filterExpressions: [],
});
}
const exprSet = new Set(exprs);
return new Query({
...this.state,
filterExpressions: this.state.filterExpressions.filter(expr => {
return !Object.keys(expr).some(key => exprSet.has(key));
}),
});
}

onlySync;
query;
export class LiveQuery<TResponse = unknown> {
private _unsubscribeSyncEvent: () => void | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Clarify type annotation of _unsubscribeSyncEvent to avoid confusion.

Currently, _unsubscribeSyncEvent is typed as () => void | null, which can be confusing because it's unclear if null refers to the function itself or its return value. It would be clearer to use (() => void) | null to indicate that _unsubscribeSyncEvent is either a function that returns void or null.

Apply this diff to clarify the type annotation:

-private _unsubscribeSyncEvent: () => void | null;
+private _unsubscribeSyncEvent: (() => void) | null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private _unsubscribeSyncEvent: () => void | null;
private _unsubscribeSyncEvent: (() => void) | null;
🧰 Tools
🪛 Biome

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

Comment on lines 344 to 346
{
onData: data =>
dispatch({ type: 'set-transactions', transactions: data }),
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure unsubscribe is defined before use in cleanup function

In the useEffect starting at line 323, unsubscribe is assigned within a Promise's then block. If the component unmounts before the Promise resolves, unsubscribe may be undefined, which could lead to errors in the cleanup function when attempting to call it.

To prevent this potential issue, initialize unsubscribe to a no-op function to ensure it's always callable:

 useEffect(() => {
   let current = true;
-  let unsubscribe;
+  let unsubscribe = () => {};

   if (state.schedule && state.transactionsMode === 'matched') {
     // existing code...
   }

   return () => {
     current = false;
-    if (unsubscribe) {
       unsubscribe();
-    }
   };
 }, [state.schedule, state.transactionsMode, state.fields]);

Committable suggestion was skipped due to low confidence.

@@ -816,13 +816,15 @@ type SelectedItemsButtonProps<Name extends string> = {
name: ((count: number) => string) | string;
items: MenuItem<Name>[];
onSelect: (name: Name, items: string[]) => void;
isLoading: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Make isLoading optional in SelectedItemsButtonProps to prevent breaking changes

Adding isLoading as a required property in SelectedItemsButtonProps may lead to TypeScript errors in components that use SelectedItemsButton without specifying this new prop. Since you've provided a default value of false for isLoading in the function signature, you can make it optional in the type definition to maintain backward compatibility.

Apply this diff to make isLoading optional:

-  isLoading: boolean;
+  isLoading?: boolean;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isLoading: boolean;
isLoading?: boolean;

Comment on lines +362 to +376
isLoading={isLoading}
getTransaction={id => transactions.find(t => t.id === id)}
onShow={onShowTransactions}
onDuplicate={onBatchDuplicate}
onDelete={onBatchDelete}
onEdit={onBatchEdit}
onLinkSchedule={onBatchLinkSchedule}
onUnlinkSchedule={onBatchUnlinkSchedule}
onCreateRule={onCreateRule}
onSetTransfer={onSetTransfer}
onScheduleAction={onScheduleAction}
showMakeTransfer={showMakeTransfer}
onMakeAsSplitTransaction={onMakeAsSplitTransaction}
onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize transaction lookup in getTransaction function

The getTransaction prop is receiving a function that searches the transactions array for a transaction by ID. Since transactions is now a readonly array and could be large, consider optimizing this lookup by using a Map or memoization to improve performance.

@@ -18,7 +19,7 @@
import { useLocalPref } from '../../hooks/useLocalPref';
import { useSplitsExpanded } from '../../hooks/useSplitsExpanded';
import { useSyncServerStatus } from '../../hooks/useSyncServerStatus';
import { AnimatedLoading } from '../../icons/AnimatedLoading';
// import { AnimatedLoading } from '../../icons/AnimatedLoading';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused import of AnimatedLoading

The import statement for AnimatedLoading is commented out. If AnimatedLoading is no longer used in this file, consider removing the import statement entirely to keep the code clean.

Apply this diff to remove the commented-out import:

-// import { AnimatedLoading } from '../../icons/AnimatedLoading';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// import { AnimatedLoading } from '../../icons/AnimatedLoading';

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: 4

🧹 Outside diff range and nitpick comments (25)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)

20-22: LGTM: Improved hook usage with query optimization.

The changes enhance the useSchedules hook usage by adding a memoized query and handling the loading state. This aligns with best practices for React hooks and optimizes performance.

Consider extracting the memoized query to a separate variable for improved readability:

+const schedulesQuery = useMemo(() => q('schedules').select('*'), []);
 const { schedules = [], isLoading } = useSchedules({
-  query: useMemo(() => q('schedules').select('*'), []),
+  query: schedulesQuery,
 });

24-26: LGTM: Added loading state handling.

The addition of the loading state check is a good practice, preventing the component from rendering with incomplete data.

Consider adding a loading indicator for a better user experience:

 if (isLoading) {
-  return null;
+  return <LoadingSpinner />; // Assuming you have a LoadingSpinner component
 }
packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (2)

36-40: LGTM: Improved useSchedules hook usage with query optimization.

The changes to the useSchedules hook usage are well-implemented:

  • Destructuring isLoading (renamed to isSchedulesLoading) and schedules improves readability.
  • Using a memoized query parameter with useMemo is a good optimization.

Minor suggestion: Consider adding a comment explaining the purpose of the query, especially if it's a complex one.


47-48: LGTM: Improved robustness with optional chaining.

The use of optional chaining when accessing schedule properties is a good practice:

  • It prevents potential runtime errors when properties are undefined.
  • The code is more concise and readable.

Suggestion: Consider adding a fallback value for the schedule name and date to improve the user experience when data is unavailable.

Example:

title={<ModalTitle title={schedule?.name || 'Untitled Schedule'} shrinkOnOverflow />}
// ...
{format(schedule?.next_date || new Date(), 'MMMM dd, yyyy')}

Also applies to: 54-54, 68-68

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (1)

51-67: LGTM with a minor suggestion: useEffect for sync events updated correctly.

The useEffect hook for handling sync events has been refactored effectively:

  • It now uses the reloadTransactions function from the useTransactions hook.
  • The dependency array correctly includes both dispatch and reloadTransactions.

However, there's a minor improvement that could be made:

Consider removing the optional chaining operator (?.) when calling reloadTransactions() on line 59. Since reloadTransactions is destructured from the useTransactions hook, it should always be defined. The optional chaining is unnecessary and might hide potential issues if reloadTransactions is unexpectedly undefined.

-          reloadTransactions?.();
+          reloadTransactions();
packages/loot-core/src/client/data-hooks/schedules.tsx (3)

26-31: Improved function signature enhances flexibility and type safety.

The updates to the loadStatuses function signature are beneficial:

  • Addition of onError callback improves error handling.
  • upcomingLength parameter allows for more flexible data processing.
  • readonly type for schedules enforces immutability.

These changes enhance the function's flexibility and type safety.

Consider adding type annotations for onData and onError callbacks for improved type safety:

function loadStatuses(
  schedules: readonly ScheduleEntity[],
  onData: (data: ScheduleStatuses) => void,
  onError: (error: Error) => void,
  upcomingLength: string
): LiveQuery<TransactionEntity>

54-126: Significant improvements to useSchedules hook structure and type safety.

The changes to the useSchedules hook are substantial and beneficial:

  • New type definitions (UseSchedulesProps, ScheduleData, UseSchedulesResult) improve type safety and readability.
  • Use of useRef for query references ensures proper cleanup and prevents memory leaks.
  • The effect cleanup now properly unsubscribes from both queries.
  • The hook returns more detailed information including loading state and error.

These changes greatly enhance the hook's functionality and maintainability.

Consider adding a check to prevent unnecessary state updates when the component is unmounted:

if (!isUnmounted) {
  setError(error);
}

This can be applied to the setError call in the onError callback of the liveQuery call.


154-176: Well-structured accountSchedulesQuery function with flexible filtering.

The new accountSchedulesQuery function is a valuable addition:

  • It provides a flexible way to build queries for schedules based on account ID.
  • Special cases like 'uncategorized' accounts are handled appropriately.
  • The use of accountFilter aligns with the import changes at the beginning of the file.

This function enhances the querying capabilities for schedules.

Consider extracting the account types into a union type for better readability and maintainability:

type AccountQueryType = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized';

export function accountSchedulesQuery(accountId?: AccountQueryType) {
  // ... rest of the function
}
packages/desktop-client/src/components/accounts/Reconcile.tsx (3)

20-21: LGTM! Consider adding JSDoc comments for improved documentation.

The changes to ReconcilingMessageProps look good. The addition of accountId and simplification of balanceQuery align well with the PR objectives.

Consider adding JSDoc comments to these props for better documentation:

type ReconcilingMessageProps = {
  /** The ID of the account being reconciled */
  accountId: AccountEntity['id'] | string;
  /** The query used to fetch the balance */
  balanceQuery: Query;
  // ... other props
};

28-28: LGTM! Consider using a type assertion for improved type safety.

The changes in the ReconcilingMessage component correctly implement the new accountId prop and update the useSheetValue hook usage. The query simplification is a good improvement.

For improved type safety, consider using a type assertion for the name property in useSheetValue:

name: `balance-query-${accountId}-cleared` as const,

This ensures that the string literal type is preserved, which can be helpful for type checking in other parts of the application.

Also applies to: 35-38


Line range hint 124-129: Update ReconcileMenu to align with new balanceQuery structure.

The ReconcileMenu component hasn't been updated to reflect the changes made in ReconcilingMessage. This could lead to inconsistencies in how balanceQuery is used across the application.

Consider updating the ReconcileMenu component to use the new balanceQuery structure:

const balanceQuery = queries.accountBalance(account);
const clearedBalance = useSheetValue<'account', `balance-${string}-cleared`>({
  name: `balance-${account.id}-cleared` as const,
  value: null,
  query: balanceQuery.filter({ cleared: true }),
});

This change would ensure consistency with the updates made in ReconcilingMessage and align with the overall PR objectives.

packages/desktop-client/src/components/accounts/Balance.jsx (2)

Line range hint 45-76: Approve changes with a minor suggestion.

The migration from account to accountId and the introduction of useCachedSchedules align well with the PR objectives. The new implementation improves performance and correctly handles schedule balances for different accounts.

However, there's a minor issue that was previously flagged:

Initialize scheduleBalance to 0 instead of null to prevent potential NaN issues in calculations:

-let scheduleBalance = null;
+let scheduleBalance = 0;

This change ensures correct calculations when adding to scheduleBalance.


Line range hint 147-230: Approve changes with a minor suggestion for CSS.

The modifications to the Balances component significantly improve its functionality and align well with the PR objectives:

  1. The new props (accountId, balanceQuery, etc.) enhance the component's flexibility.
  2. Using ButtonWithLoading improves user experience during data fetching.
  3. The useMemo hook for balanceBinding optimizes performance.
  4. Conditional rendering of MoreBalances and FilteredBalance is more explicit and maintainable.

However, there's a minor suggestion for the CSS:

Consider using object notation for CSS properties that use kebab-case in JavaScript:

 className={css({
   paddingTop: 1,
   paddingBottom: 1,
   [`& svg`]: {
     width: 10,
     height: 10,
     marginLeft: 10,
     color: theme.pillText,
-    transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)',
+    transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0deg)',
     opacity: selectedItems.size > 0 || showExtraBalances ? 1 : 0,
   },
   [`&[data-hovered] svg`]: {
     opacity: 1,
   },
 })}

This change ensures consistency in the rotation values and improves readability.

packages/loot-core/src/client/data-hooks/transactions.ts (2)

91-180: LGTM: Well-implemented preview transactions hook with a minor optimization suggestion.

The usePreviewTransactions hook is well-implemented, handling various states appropriately and using useMemo for optimization. The error handling is comprehensive.

A minor optimization suggestion:

Consider memoizing the previewTransactions state setter function to avoid unnecessary re-renders:

- const [previewTransactions, setPreviewTransactions] = useState<
-   TransactionEntity[]
- >([]);
+ const [previewTransactions, setPreviewTransactions] = useState<TransactionEntity[]>([]);
+ const memoizedSetPreviewTransactions = useCallback((transactions: TransactionEntity[]) => {
+   setPreviewTransactions(ungroupTransactions(transactions));
+ }, []);

  // ... in the effect
- setPreviewTransactions(ungroupTransactions(withDefaults));
+ memoizedSetPreviewTransactions(withDefaults);

This change is optional and may provide a slight performance boost in scenarios with frequent updates.


182-232: LGTM: Well-implemented search hook with a suggestion for enhanced type safety.

The useTransactionsSearch hook and the isForPreview helper function are well-implemented. The use of debounce and useCallback in the search hook is appropriate for optimizing performance.

To enhance type safety, consider adding a type for the searchText parameter in the updateSearchQuery callback:

  const updateSearchQuery = debounce(
    useCallback(
-     (searchText: string) => {
+     (searchText: string | undefined) => {
        if (searchText === '') {
          resetQuery();
          setIsSearching(false);
-       } else if (searchText) {
+       } else if (searchText && searchText.length > 0) {
          updateQuery(previousQuery =>
            queries.transactionsSearch(previousQuery, searchText, dateFormat),
          );
          setIsSearching(true);
        }
      },
      [dateFormat, resetQuery, updateQuery],
    ),
    delayMs,
  );

This change ensures that the function handles potential undefined inputs correctly and only performs a search when there's actual text to search for.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (4)

59-63: Improved schedule querying and optimization

The replacement of useDefaultSchedulesQueryTransform with a memoized accountSchedulesQuery is a good optimization. It ensures the query is only recreated when accountId changes. The SchedulesProvider update to use a query prop instead of a transform prop aligns well with the new data fetching patterns.

Consider updating the component props type definition to be more specific:

type AccountTransactionsProps = {
  readonly account?: AccountEntity;
  readonly accountId?: AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized';
  readonly accountName: AccountEntity['name'] | string;
};

This change would make the component's interface more clear and type-safe.

Also applies to: 81-87


Line range hint 106-155: Performance optimization with useCallback

The use of useCallback for onSave, onEditNotes, onCloseAccount, and onReopenAccount functions is a good practice for performance optimization. This can help prevent unnecessary re-renders in child components that receive these callbacks as props.

The memoization of onClick and its expanded dependency list is also a positive change, ensuring the function is correctly updated when its dependencies change.

For consistency, consider also wrapping the onSaveNotes function with useCallback:

const onSaveNotes = useCallback(async (id: string, notes: string) => {
  await send('notes-save', { id, note: notes });
}, []);

This would make the pattern consistent across all callback functions in this component.


224-240: Improved transaction querying and state management

The introduction of baseTransactionsQuery and the use of useState for transactionsQuery significantly improves how transaction data is queried and managed. The useTransactions hook provides a clean way to fetch and manage transaction data, including loading states and pagination.

These changes enhance the component's flexibility and performance. The use of useCallback for baseTransactionsQuery ensures query stability across renders, which is a good optimization.

Consider adding error handling to the useTransactions hook usage:

const {
  transactions,
  isLoading,
  error,
  reload: reloadTransactions,
  loadMore: loadMoreTransactions,
} = useTransactions({
  query: transactionsQuery,
});

// Then, handle the error state in your component
if (error) {
  // Render an error message or trigger an error boundary
}

This would make the component more robust in handling potential data fetching errors.


277-305: Enhanced search functionality and transaction handling

The introduction of the useTransactionsSearch hook and the updates to the onOpenTransaction function represent significant improvements:

  1. The useTransactionsSearch hook likely provides a more efficient and reusable way to handle transaction searches, improving the overall performance and user experience.

  2. The onOpenTransaction function now correctly differentiates between regular and preview transactions, ensuring appropriate actions are taken for each type.

These changes enhance the component's functionality and user interaction.

To improve code readability, consider extracting the preview transaction handling logic into a separate function:

const handlePreviewTransaction = useCallback((transactionId: string) => {
  const parts = transactionId.split('/');
  dispatch(
    pushModal('scheduled-transaction-menu', {
      transactionId,
      onPost: async () => {
        await send('schedule/post-transaction', { id: parts[1] });
        dispatch(collapseModals('scheduled-transaction-menu'));
      },
      onSkip: async () => {
        await send('schedule/skip-next-date', { id: parts[1] });
        dispatch(collapseModals('scheduled-transaction-menu'));
      },
    })
  );
}, [dispatch]);

// Then in onOpenTransaction:
if (isPreviewId(transaction.id)) {
  handlePreviewTransaction(transaction.id);
} else {
  navigate(`/transactions/${transaction.id}`);
}

This separation of concerns would make the onOpenTransaction function more readable and maintainable.

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

243-243: Correct adjustment to parseCategoryFields function.

The function signature change aligns with the new structure of categories obtained from the useCategories hook. This maintains the existing functionality while adapting to the new data structure.

Consider optimizing this function by using a Map for O(1) lookup:

function parseCategoryFields(trans, categories) {
  const categoryMap = new Map(categories.map(cat => [cat.name, cat.id]));
  return categoryMap.get(trans.category) || null;
}

This approach would be more efficient, especially for large category lists.

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

819-819: LGTM! Good addition of loading state.

The introduction of the isLoading prop and the use of ButtonWithLoading component improves user feedback during asynchronous operations. The default value of false for isLoading ensures backward compatibility.

Consider using a more specific type for the isLoading prop in the SelectedItemsButtonProps interface:

- isLoading: boolean;
+ isLoading?: boolean;

This change would make the type definition consistent with the default value in the function signature.

Also applies to: 827-827, 842-856

packages/desktop-client/src/components/modals/EditRuleModal.jsx (2)

306-323: Great refactoring of the ScheduleDescription component.

The changes to use the new useSchedules hook are well-implemented. The destructuring of schedules, statuses, and isLoading improves code readability and aligns with the PR objectives.

Consider adding an error state check for better user experience:

  if (isSchedulesLoading) {
    return null;
  }

+ if (error) {
+   return <View style={{ flex: 1 }}>Error loading schedule: {error.message}</View>;
+ }

  if (schedules.length === 0) {
    return <View style={{ flex: 1 }}>{id}</View>;
  }

This addition would handle potential errors from the useSchedules hook, providing better feedback to the user.


Line range hint 1-1000: Overall, the changes improve the component's structure and align with PR objectives.

The primary modification in this file is the refactoring of the ScheduleDescription component to use the new useSchedules hook. This change aligns well with the PR's goal of migrating to functional components and utilizing the new hook.

The rest of the file remains largely unchanged, which is good as it minimizes the risk of introducing unintended side effects. The refactoring improves code readability and maintainability without altering the overall functionality of the EditRuleModal component.

To further improve the code:

  1. Consider adding JSDoc comments for the main components and functions to enhance documentation.
  2. Evaluate if any of the nested components (e.g., ConditionEditor, ActionEditor) could be moved to separate files to reduce the overall file size and improve modularity.
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)

Line range hint 1765-1775: Enhanced loading state handling in TransactionTableInner

The addition of the isLoading prop to the TransactionTableInner component and its propagation to the Table component is a positive change. This allows for better management of loading states throughout the transaction table, potentially improving user experience by providing visual feedback during data fetching or processing.

For consistency, consider destructuring the isLoading prop along with the other props at the beginning of the function, like this:

function TransactionTableInner({
  isLoading,
  tableNavigator,
  tableRef,
  listContainerRef,
  dateFormat = 'MM/dd/yyyy',
  newNavigator,
  renderEmpty,
  onScroll,
  ...props
}) {
  // ... rest of the component
}

This would align with the component's existing style and make it clearer that isLoading is an expected prop.

Also applies to: 1994-2005

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)

Line range hint 261-277: Ensure type consistency when passing items to the Table component

The items array includes both ScheduleEntity and CompletedScheduleItem types, but it is being cast to ScheduleEntity[] when passed to the Table component. This may lead to type safety issues or runtime errors if the Table component expects only ScheduleEntity objects.

Consider updating the Table component's typing to accept SchedulesTableItem[] instead of casting items to ScheduleEntity[]. Alternatively, adjust the items array to include only ScheduleEntity objects before passing it to the Table component.

Apply this diff to pass items without casting:

-            items={items as ScheduleEntity[]}
+            items={items}

And ensure the Table component can handle SchedulesTableItem[] appropriately.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between fddd46f and 8ed0a14.

📒 Files selected for processing (23)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/desktop-client/src/components/ManageRules.tsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/schedules/index.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (2)
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
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.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
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.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (48)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)

Line range hint 1-38: Summary: Successfully migrated to functional component with new hook usage.

The ScheduleValue component has been successfully updated to use the new useSchedules hook, aligning with the PR objectives. Key improvements include:

  1. Optimized query creation with useMemo.
  2. Proper handling of loading states.
  3. Default value for schedules to prevent potential errors.

However, there's a TODO item regarding type coercion that should be addressed in a follow-up task.

Overall, the changes improve the component's performance and maintainability.


Line range hint 32-38: Address TODO: Investigate type coercion in schedule._payee.

The TODO comment highlights a potential type issue with schedule._payee. This should be investigated and addressed to improve type safety.

Let's verify the usage of schedule._payee across the codebase:

Based on the results, consider refactoring to use schedule._payee.id if appropriate, or add proper type assertions/guards to ensure type safety.

Would you like assistance in refactoring this part of the code once the investigation is complete?

packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3)

2-2: LGTM: Import statements updated correctly.

The addition of useMemo and the import of q from the query module align well with the changes in how schedules are fetched and optimized.

Also applies to: 9-9


43-45: LGTM: Proper handling of loading state.

The addition of the loading state check is a good practice. Returning null while the schedules are loading prevents rendering incomplete or incorrect content.


Line range hint 1-124: Overall assessment: Well-implemented changes improving component robustness and alignment with modern practices.

The modifications to ScheduledTransactionMenuModal.tsx successfully achieve the following:

  1. Improved data fetching with the updated useSchedules hook.
  2. Better handling of loading states and potential undefined values.
  3. Use of React hooks and modern JavaScript features (optional chaining, useMemo).

These changes align well with the PR objectives of migrating to functional components and using the new hooks. The code is now more robust and maintainable.

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (7)

1-1: LGTM: Import statements updated correctly.

The new imports for useTransactions and useTransactionsSearch are correctly added, aligning with the migration to functional components and the use of new hooks as per the PR objectives.

Also applies to: 5-8


28-35: LGTM: baseTransactionsQuery function refactored correctly.

The baseTransactionsQuery function has been appropriately refactored:

  • It's now memoized using useCallback, which can improve performance.
  • The dependency array correctly includes category and month.
  • The query structure is aligned with the component's purpose.

37-47: LGTM: Transaction state management refactored effectively.

The state management for transactions has been significantly improved:

  • The useState hook is used to manage the transactionsQuery state.
  • The useTransactions custom hook is implemented correctly, simplifying the logic for fetching and managing transactions.
  • The destructuring of values from useTransactions provides clear access to necessary functions and data (transactions, isLoading, loadMore, reload).

This refactoring aligns well with React best practices for functional components and the PR objectives.


69-73: LGTM: useTransactionsSearch hook implemented correctly.

The useTransactionsSearch hook has been implemented effectively:

  • It manages the search functionality and updates the transactions query as needed.
  • The updateQuery and resetQuery callbacks are properly defined, using the setTransactionsQuery state setter and baseTransactionsQuery function respectively.
  • The dateFormat is correctly passed to the hook.

This implementation aligns well with the refactoring goals and simplifies the search functionality management.


75-82: LGTM: onOpenTransaction callback refactored effectively.

The onOpenTransaction callback has been well-refactored:

  • It's now memoized using useCallback, which can help optimize performance.
  • The dependency array correctly includes the navigate function.
  • The logic for handling transactions has been simplified while maintaining the necessary check for preview transactions.
  • The comment on line 77 provides useful context about the previous implementation for native app handling of preview transactions.

This refactoring aligns well with React best practices and the overall goals of the PR.


115-116: LGTM: TransactionListWithBalances props updated correctly.

The props passed to the TransactionListWithBalances component have been updated appropriately:

  • The new isLoading prop has been added, allowing for better handling of loading states.
  • The onLoadMore prop now correctly uses the loadMoreTransactions function from the useTransactions hook.
  • The onOpenTransaction prop uses the memoized onOpenTransaction callback.

These changes are consistent with the new hook-based approach for managing transactions and align well with the overall refactoring goals.


Line range hint 1-127: Overall assessment: Excellent refactoring of the CategoryTransactions component.

The migration of the CategoryTransactions component to a functional component using the new useTransactions and useTransactionsSearch hooks has been executed very well. The changes align perfectly with the PR objectives and demonstrate a good understanding of React best practices. Key improvements include:

  1. Simplified state management using custom hooks.
  2. Improved performance through the use of useCallback where appropriate.
  3. Cleaner and more maintainable code structure.
  4. Consistent use of the new hook-based approach throughout the component.

The only minor suggestion is to remove the optional chaining operator when calling reloadTransactions(). Overall, this refactoring significantly enhances the component's quality and maintainability.

packages/loot-core/src/client/data-hooks/schedules.tsx (5)

7-20: Improved imports enhance type safety and align with codebase updates.

The changes to the import statements are well-considered:

  • Addition of useRef suggests better management of mutable values.
  • PropsWithChildren import improves type safety for components with children.
  • New entity type imports (TransactionEntity, ScheduleEntity, AccountEntity) enhance type checking.
  • The switch to accountFilter aligns with updates in other parts of the codebase.

These changes contribute to better type safety and code consistency.


32-50: Improved data handling and error management in loadStatuses function.

The changes to the loadStatuses function implementation are well-structured:

  • Creation of scheduleStatuses map improves data organization.
  • Proper use of onData and onError callbacks enhances error handling and data flow.
  • The implementation aligns well with the updated function signature.

These changes contribute to better data management and error handling within the function.


131-135: Enhanced SchedulesContext with improved type safety.

The updates to SchedulesContext are well-considered:

  • The context value type now includes isLoading and error states, aligning with the hook's return type.
  • Default values are provided for all properties, ensuring type safety when accessing the context.

These changes improve the consistency between the hook and the context, enhancing overall type safety.


Line range hint 137-149: Improved SchedulesProvider with better type safety and consistency.

The changes to SchedulesProvider are beneficial:

  • Use of PropsWithChildren improves type safety for components with children.
  • The switch from transform prop to query prop aligns with the changes in the useSchedules hook.

These updates enhance type safety and maintain consistency with the hook changes.


Line range hint 1-176: Comprehensive improvements enhancing type safety, error handling, and query management.

This file has undergone significant positive changes:

  1. Enhanced type safety with new type definitions and proper use of TypeScript features.
  2. Improved error handling throughout the useSchedules hook and related functions.
  3. Better query management using useRef for proper cleanup and prevention of memory leaks.
  4. Alignment with updates in other parts of the codebase, such as the use of accountFilter.
  5. Improved flexibility in schedule querying with the new accountSchedulesQuery function.

These changes collectively contribute to a more robust, maintainable, and type-safe implementation of schedule-related functionality.

Great job on these improvements! The code is now more resilient and easier to maintain.

packages/desktop-client/src/components/accounts/Reconcile.tsx (1)

Line range hint 1-180: Summary: Good progress, but ensure consistency across components.

Overall, the changes in this file align well with the PR objectives of migrating to functional components and using new hooks. The ReconcilingMessage component and its props have been successfully updated to use the new structure.

Key points:

  1. The addition of accountId and simplification of balanceQuery in ReconcilingMessageProps is a good improvement.
  2. The ReconcilingMessage component correctly implements these changes.
  3. The ReconcileMenu component needs to be updated to align with the new balanceQuery structure for consistency.

Please address the suggested updates to ReconcileMenu to ensure full alignment with the new structure across the entire file.

To ensure all necessary updates have been made, please run the following verification script:

packages/desktop-client/src/components/accounts/Balance.jsx (2)

117-124: LGTM: Improved clarity and error prevention.

The changes to FilteredBalance enhance both clarity and robustness:

  1. Renaming filteredAmount to filteredBalance improves readability.
  2. Using filteredBalance || 0 prevents potential issues with undefined values.

These modifications align well with best practices for maintainable and error-resistant code.


128-137: LGTM: Enhanced specificity in balance queries.

The changes to MoreBalances improve the function's ability to handle multiple accounts:

  1. Adding accountId as a parameter allows for account-specific queries.
  2. Including accountId in useSheetValue names prevents potential naming conflicts.

These modifications enhance the component's flexibility and maintainability.

packages/loot-core/src/client/data-hooks/transactions.ts (2)

1-30: LGTM: Imports and type definitions are well-structured.

The imports and type definitions are appropriate for the functionality of the hooks. The types provide good type safety and clarity for the data structures used in the hooks.


1-232: Summary: Well-implemented transaction hooks aligning with PR objectives

This new file introduces well-structured and optimized hooks for transaction management, aligning perfectly with the PR's objective of simplifying transaction fetching across the codebase. The useTransactions, usePreviewTransactions, and useTransactionsSearch hooks provide a solid foundation for managing transactions in functional components.

The implementation demonstrates good use of React hooks, proper error handling, and performance optimizations. The minor issues and optimization suggestions provided in the review comments, if addressed, will further enhance the robustness and efficiency of these hooks.

Overall, this file significantly contributes to the migration from class-based components to functional components and the adoption of the new useTransactions hook, as outlined in the PR objectives.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (4)

21-28: Improved data management with new hooks

The introduction of accountSchedulesQuery and new transaction-related hooks (usePreviewTransactions, useTransactions, useTransactionsSearch) indicates a positive shift towards more modular and reusable data fetching logic. This change likely improves separation of concerns and may enhance performance.


217-222: Improved type safety for component props

The changes to the TransactionListWithPreviews component props enhance type safety:

  1. The accountId prop now includes specific string literals ('budgeted', 'offbudget', 'uncategorized') in addition to the AccountEntity['id'] type. This prevents potential errors by ensuring only valid account identifiers are passed.

  2. The accountName prop type now uses AccountEntity['name'] | string, which allows for both account names from the AccountEntity type and custom string names. This flexibility is useful while maintaining type safety.

These improvements make the component's interface more precise and reduce the likelihood of type-related errors.


Line range hint 1-362: Overall assessment: Significant improvements in component structure and functionality

The changes made to the AccountTransactions component and its subcomponents represent a substantial improvement in several areas:

  1. Enhanced data management through the introduction of new hooks (useTransactions, useTransactionsSearch, etc.).
  2. Improved performance optimizations with proper use of useCallback and useMemo.
  3. Better type safety with more precise prop types and union types.
  4. Refactored transaction querying and state management for more efficient data handling.
  5. Updated search functionality and transaction opening logic for a better user experience.

These changes collectively contribute to a more maintainable, performant, and type-safe component. The suggested minor improvements, if implemented, will further enhance the code quality.

Great job on this refactoring effort!


313-316: ⚠️ Potential issue

Fix typo and approve transaction display logic

The logic for combining preview and regular transactions based on the search state is correct. However, there's a typo in the variable name that needs to be fixed:

- const transcationsToDisplay = !isSearching
+ const transactionsToDisplay = !isSearching
    ? previewTransactions.concat(transactions)
    : transactions;

This typo could lead to confusion and potential bugs, so it's important to correct it.

The logic itself is sound, displaying both preview and regular transactions when not searching, and only regular transactions during a search operation.

packages/desktop-client/src/components/accounts/Header.tsx (6)

11-11: New Query type import added

The addition of the Query type import from 'loot-core/shared/query' suggests a move towards more structured query handling. This is a positive change that should improve type safety and consistency in query-related operations.


22-22: Unused import removed

The commented-out import of AnimatedLoading has been addressed as per the previous review comment. To keep the code clean, you can now safely remove this line entirely.


56-57: AccountHeaderProps type updated

The AccountHeaderProps type has been updated with several new properties:

  • isLoading: boolean
  • accountId: AccountEntity['id'] | string
  • transactions: readonly TransactionEntity[]
  • balanceQuery and filteredQuery: Query
  • showFilteredBalance and filteredBalance

These changes reflect updates in the component's functionality and data handling. The use of readonly for transactions is a good practice for immutability.

Also applies to: 65-65, 71-72, 75-76


133-134: AccountHeader component updated with new props and local search state

The AccountHeader component has been updated to include new props such as isLoading, accountId, balanceQuery, showFilteredBalance, and filteredBalance. These additions align with the changes made to the AccountHeaderProps type.

A new local state variable search has been added using the useState hook. This change suggests a shift towards managing search state locally within the component, which can improve performance and reduce unnecessary re-renders of parent components.

Also applies to: 148-153, 199-199


361-376: SelectedTransactionsButton component updated with new props

The SelectedTransactionsButton component has been updated with several new props:

  • isLoading: Reflects the loading state from the parent component.
  • getTransaction: Now uses the readonly transactions array, promoting immutability.
  • New props like onDuplicate, onLinkSchedule, onUnlinkSchedule, onSetTransfer, onMakeAsSplitTransaction, and onMakeAsNonSplitTransactions have been added.

These changes suggest enhanced functionality for transaction management, including support for split transactions and scheduling. The use of the readonly transactions array in the getTransaction prop is a good practice for maintaining data immutability.


296-300: Balances component updated with new props

The Balances component now receives additional props:

  • accountId
  • filteredBalance
  • showFilteredBalance

These new props suggest enhanced functionality for displaying account balances, potentially including filtered balance information. However, the isLoading prop has been commented out. Ensure that loading states are still being handled appropriately, either within the Balances component or at a higher level in the component tree.

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

8-8: Excellent use of the new useCategories hook!

The integration of the useCategories hook centralizes category management, promoting a single source of truth for categories. This change enhances maintainability and consistency across the application.

Also applies to: 158-159


298-298: Correct updates to getImportPreview function.

The changes in the category assignment logic and the addition of categories to the dependencies array are appropriate. This ensures that the function works correctly with the new category structure and re-creates when categories change.

Also applies to: 313-313


587-587: Appropriate update to category assignment in onImport function.

The change in category assignment logic aligns with the new category structure, ensuring consistency throughout the import process.


789-789: Correct update to categories prop in Transaction component.

The change in the categories prop passed to the Transaction component is consistent with the new category data structure, ensuring that child components receive the correct data.


Line range hint 1-1092: Overall excellent refactoring to integrate the useCategories hook.

The changes in this file consistently update the handling of categories throughout the ImportTransactionsModal component. By integrating the useCategories hook, the code now benefits from centralized category management, which improves maintainability and ensures a single source of truth for category data.

Key improvements:

  1. Consistent updates to function signatures and logic to work with the new category structure.
  2. Proper integration of the useCategories hook in the component.
  3. Updates to props passed to child components, ensuring data consistency.

These changes enhance the overall architecture of the application while maintaining existing functionality. Great job on this refactoring effort!

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

Line range hint 1-1293: Summary of changes

The main modifications in this file are:

  1. Addition of loading state functionality to the SelectedItemsButton component, improving user feedback during asynchronous operations.
  2. No significant changes observed in the Table component within the provided code snippet.

Overall, the changes appear to enhance the user experience without introducing any major issues. The addition of the loading state is a positive improvement.


Line range hint 1-1293: No significant changes observed in the Table component.

The AI-generated summary mentioned updates to the Table component, but no substantial changes are visible in the provided code snippet. If there were intended changes to the Table component, they might be in parts of the file not shown here.

To confirm if there are any changes to the Table component that are not visible in the provided snippet, we can run the following script:

This script will help us verify if there are any modifications to the Table component that we might have missed in the provided code snippet.

packages/desktop-client/src/components/modals/EditRuleModal.jsx (1)

1-1: LGTM: Import statements look good.

The import statements are well-organized and include all necessary dependencies for the component.

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)

769-776: Improved schedule handling in PayeeIcons component

The changes to the PayeeIcons component enhance its functionality and performance:

  1. The useCachedSchedules hook is now used to manage the loading state and retrieve schedules.
  2. The component now handles the loading state gracefully by returning null when isLoading is true.
  3. The schedule is now derived from the cached schedules array, potentially reducing prop drilling and improving data consistency.

These improvements should lead to better performance and a more robust handling of schedule-related data in the component.


Line range hint 1-2405: Overall improvements in loading state and schedule data handling

The changes made to this file, particularly in the PayeeIcons and TransactionTableInner components, represent a positive step towards better handling of asynchronous data and loading states. Key improvements include:

  1. Use of useCachedSchedules hook in PayeeIcons for more efficient schedule data management.
  2. Graceful handling of loading states in PayeeIcons.
  3. Addition of isLoading prop to TransactionTableInner for propagating loading state to child components.

These enhancements should result in:

  • Improved performance due to better caching and state management.
  • Enhanced user experience with more responsive loading indicators.
  • More robust handling of asynchronous data throughout the transaction table.

While no major new features were introduced, these changes contribute to the overall stability and responsiveness of the component.

packages/desktop-client/src/hooks/usePreviewTransactions.ts (1)

27-66: LGTM!

The updates to the usePreviewTransactions hook enhance the efficiency and readability of the code. The use of usePrevious to track changes in scheduleData and the added loading check improve the hook's performance.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (5)

39-40: Good addition of isLoading prop and enforcing immutability

Adding the optional isLoading prop enhances the component's ability to manage loading states effectively. Changing schedules to readonly ScheduleEntity[] enforces immutability, which is a best practice in React and TypeScript to prevent unintended mutations.


207-207: Setting a default value for isLoading prop

Providing a default value of false for isLoading ensures that the component functions correctly even if the prop is not supplied, improving the robustness of the code.


278-279: Optimize renderSchedule with useCallback

Wrapping renderSchedule in useCallback improves performance by memoizing the function, preventing unnecessary re-creations on each render when dependencies haven't changed.


345-346: Improve renderItem performance with useCallback

Using useCallback for renderItem enhances performance by memoizing the function, ensuring it only changes when its dependencies do.


406-406: Pass isLoading to the Table component for proper loading indication

Including the loading={isLoading} prop ensures the Table component properly reflects the loading state, enhancing the user experience during data fetch operations.

@@ -1,11 +1,12 @@
import React from 'react';
import React, { useMemo } from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Issue Found: Invalid import paths in ScheduleValue.tsx.

The import paths 'loot-core/client/data-hooks/schedules' and 'loot-core/shared/query' do not exist, causing module resolution errors.

🔗 Analysis chain

LGTM: Import changes are consistent with component updates.

The import changes align with the component's new requirements and the broader refactoring mentioned in the AI summary.

Let's verify the new import paths:

Also applies to: 3-4

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and usage of the imported modules.

# Test 1: Check if 'useSchedules' is exported from the new path
rg -p "export.*useSchedules" "loot-core/client/data-hooks/schedules"

# Test 2: Check if 'q' is exported from the specified path
rg -p "export.*q" "loot-core/shared/query"

# Test 3: Verify consistent usage of 'useSchedules' across the codebase
rg "from 'loot-core/client/data-hooks/schedules'" -g '*.{ts,tsx,js,jsx}'

Length of output: 976

@@ -669,7 +674,7 @@ type AccountMenuProps = {
account: AccountEntity;
canSync: boolean;
showBalances: boolean;
canShowBalances: boolean;
// canShowBalances: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Cleanup of commented-out canShowBalances references needed

The canShowBalances property has been successfully removed from active code within the AccountMenu component. However, several commented-out references to canShowBalances still exist in packages/desktop-client/src/components/accounts/Header.tsx. Please remove these commented lines to ensure the codebase remains clean and maintainable.

  • packages/desktop-client/src/components/accounts/Header.tsx:677
  • packages/desktop-client/src/components/accounts/Header.tsx:699
  • packages/desktop-client/src/components/accounts/Header.tsx:722-737
🔗 Analysis chain

AccountMenu component updated to always show balance toggle option

The AccountMenu component has been modified to always include the option to toggle balance visibility:

  • The canShowBalances prop has been removed.
  • The menu items for toggling balance visibility are now always included, regardless of any condition.

This change suggests that the ability to show/hide balances is now always available to users. Ensure that this aligns with the intended user experience and that there are no scenarios where hiding this option would be necessary.

Also applies to: 699-699, 722-737

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify if there are any remaining checks for canShowBalances in the codebase
rg "canShowBalances" --type ts --type tsx

Length of output: 223


Script:

#!/bin/bash
# Verify if there are any remaining checks for canShowBalances in the codebase
rg "canShowBalances" --type=typescript --type=typescriptreact

Length of output: 96


Script:

#!/bin/bash
# Verify if there are any remaining checks for canShowBalances in the codebase
rg "canShowBalances" -g "*.ts" -g "*.tsx"

Length of output: 422

/>
{!minimal && (
<Field width={80} style={{ textAlign: 'center' }}>
{schedule._date && schedule._date.frequency && (
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify conditional rendering using optional chaining

You can simplify the condition by using optional chaining, which makes the code cleaner and more concise.

Apply this diff to simplify the condition:

-          {schedule._date && schedule._date.frequency && (
+          {schedule._date?.frequency && (
             <SvgCheck style={{ width: 13, height: 13 }} />
           )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{schedule._date && schedule._date.frequency && (
{schedule._date?.frequency && (
🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

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: 2

🧹 Outside diff range and nitpick comments (16)
packages/loot-core/src/client/data-hooks/schedules.tsx (2)

26-50: Enhanced error handling and data processing in loadStatuses function.

The changes to the loadStatuses function improve error handling and data processing flexibility. The use of a Map for scheduleStatuses is a good choice for performance.

Consider using the nullish coalescing operator for the onData callback:

-      onData?.(scheduleStatuses);
+      onData?.(scheduleStatuses);

This ensures that onData is called only if it's defined and not null or undefined.


154-176: Well-structured and flexible accountSchedulesQuery function.

The new accountSchedulesQuery function provides a flexible and reusable way to construct queries for account schedules. It handles different account types well and aligns with the accountFilter usage earlier in the file.

Consider extracting the base query into a separate variable for improved readability:

const baseQuery = q('schedules')
  .select('*')
  .filter({
    $and: [{ '_account.closed': false }],
  });

let query = baseQuery;

// ... rest of the function

This separation makes the base query more apparent and easier to modify if needed in the future.

packages/desktop-client/src/components/accounts/Balance.jsx (3)

45-45: LGTM: SelectedBalance function improved with better parameter naming and loading state handling.

The changes improve the function by using 'accountId' instead of 'account', which is a better practice for identifying accounts. The addition of loading state handling for schedules enhances the component's reliability.

However, consider initializing scheduleBalance to 0 instead of null:

-let scheduleBalance = null;
+let scheduleBalance = 0;

This would align with the calculation logic and prevent potential NaN issues.

Also applies to: 71-77


149-152: LGTM: Balances function parameters updated for improved functionality.

The changes to the function parameters align well with the overall improvements in the component. The use of 'accountId' and 'balanceQuery' instead of 'account' provides more flexibility, and the addition of 'filteredBalance' and 'showFilteredBalance' parameters suggests new filtering capabilities.

Consider adding JSDoc comments to document these new parameters and their purpose.


Line range hint 176-226: LGTM: Improved button component with loading state and updated styling.

The changes enhance the user experience by introducing a loading state and improve code maintainability with scoped styling using @emotion/css. The updates to child components maintain consistency with the overall accountId changes.

Consider extracting the styles into a separate constant or file for better organization:

const buttonStyles = css({
  paddingTop: 1,
  paddingBottom: 1,
  // ... other styles
});

// Then use it like this:
className={buttonStyles}

This would improve readability and make the styles more reusable.

packages/loot-core/src/client/data-hooks/transactions.ts (1)

97-180: Approve implementation with a minor optimization suggestion.

The usePreviewTransactions hook is well-implemented, effectively managing preview transactions based on schedule data. The use of useMemo for scheduleTransactions is a good optimization to prevent unnecessary recalculations.

The effect that applies rules to the preview transactions is correctly implemented, including proper handling of unmounting and error states. The returned object provides all necessary data along with loading and error states.

Consider optimizing the effect dependency array on line 173:

- }, [scheduleTransactions, schedules, statuses]);
+ }, [scheduleTransactions]);

schedules and statuses are already dependencies of scheduleTransactions, so including them in the effect's dependency array is redundant. This change might prevent unnecessary effect runs.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (2)

Line range hint 106-155: Optimized callback functions with useCallback

The refactoring of callback functions using useCallback is a positive change that can help optimize performance, especially in larger components. The explicit dependency lists ensure that these callbacks are only recreated when necessary.

These changes align well with React best practices for optimizing functional components.

Consider wrapping the onSaveNotes function with useCallback as well for consistency:

-  const onSaveNotes = useCallback(async (id: string, notes: string) => {
+  const onSaveNotes = useCallback(
+    async (id: string, notes: string) => {
       await send('notes-save', { id, note: notes });
-  }, []);
+    },
+    []
+  );

224-240: Improved transaction management with custom hooks

The refactoring of the TransactionListWithPreviews component to use custom hooks (useTransactions and useTransactionsSearch) is a significant improvement. This change simplifies the component logic, improves separation of concerns, and should make the code more maintainable.

The introduction of baseTransactionsQuery ensures consistent query creation, which is a good practice.

These changes align well with React best practices and should improve the overall structure and performance of the component.

Consider renaming the transcationsToDisplay variable to transactionsToDisplay to fix the typo:

- const transcationsToDisplay = !isSearching
+ const transactionsToDisplay = !isSearching
    ? previewTransactions.concat(transactions)
    : transactions;

Also applies to: 242-243, 277-281

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)

278-343: Improved performance and readability with useCallback

The refactoring of renderSchedule into a memoized function using useCallback is a great improvement:

  1. It can prevent unnecessary re-renders, potentially improving performance.
  2. The function's logic is more concise and readable.
  3. Style properties have been updated for better consistency.

One minor suggestion:

Consider destructuring schedule in the function parameters for cleaner code:

- const renderSchedule = useCallback(
-   ({ schedule }: { schedule: ScheduleEntity }) => {
+ const renderSchedule = useCallback(
+   ({ schedule }: { schedule: ScheduleEntity }) => {

Overall, these changes enhance both performance and code quality. Well done!

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


Potential Type Safety Issue with items Casting

The items prop in SchedulesTable.tsx is being cast to ScheduleEntity[], which may hide potential type errors. Consider specifying the generic type for the Table component to ensure type safety without relying on type casting.

  • File: packages/desktop-client/src/components/schedules/SchedulesTable.tsx
  • Lines: 403-409
🔗 Analysis chain

Line range hint 406-412: Improved loading state handling and rendering logic

The changes to the main component return statement are good:

  1. Adding the loading prop to the Table component improves user experience during data fetching.
  2. Using the memoized renderItem function for rendering is consistent with earlier changes.

However, there's a potential area for improvement:

The type casting of items to ScheduleEntity[] might hide potential type errors. Consider refactoring the Table component or the items type to avoid this cast. Here's a script to verify the usage of ScheduleEntity type in the codebase:

This will help ensure type safety throughout the component. Otherwise, the changes look good and improve the overall functionality of the component.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of ScheduleEntity type and Table component props

# Test: Search for ScheduleEntity type usage
echo "ScheduleEntity type usage:"
rg "ScheduleEntity(\[])?" --type typescript

# Test: Search for Table component props
echo "\nTable component props:"
rg "Table\s*<.*?>" --type typescript

Length of output: 276


Script:

#!/bin/bash
# Description: Check usage of ScheduleEntity type and Table component props in TypeScript files

# Test: Search for ScheduleEntity type usage in .ts and .tsx files
echo "ScheduleEntity type usage:"
rg "ScheduleEntity(\[])?" -g "*.ts" -g "*.tsx"

# Test: Search for Table component props in .ts and .tsx files
echo "\nTable component props:"
rg "Table\s*<.*?>" -g "*.ts" -g "*.tsx"

Length of output: 5582

packages/desktop-client/src/components/accounts/Header.tsx (1)

22-22: Unused import removed

The import for AnimatedLoading has been commented out. If this component is no longer used in the file, it's good practice to remove the import entirely rather than commenting it out.

Consider removing the commented-out import entirely:

-// import { AnimatedLoading } from '../../icons/AnimatedLoading';
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (4)

Line range hint 243-253: Simplified parseCategoryFields function

The changes to the parseCategoryFields function align well with the new category management approach. The function is now more straightforward and flexible.

Consider using Array.find for a more concise implementation:

function parseCategoryFields(trans, categories) {
  const match = categories.find(category => category.name === trans.category);
  return match ? match.id : null;
}

This suggestion maintains the current functionality while potentially improving readability and performance.


Line range hint 298-302: Updated getImportPreview function for new category structure

The changes in the getImportPreview function correctly adapt to the new category management approach. The inclusion of categories in the dependency array of useCallback is crucial for maintaining consistency.

Consider memoizing the categories array to potentially optimize performance:

const memoizedCategories = useMemo(() => categories, [categories]);

Then use memoizedCategories in place of categories throughout the component. This could help prevent unnecessary re-renders if the categories array reference changes but its content remains the same.

Also applies to: 313-313


789-792: Updated Transaction component prop for new category structure

The changes to the Transaction component prop correctly pass the new category structure. This ensures consistency in how categories are handled throughout the component tree.

Consider memoizing the Transaction component or its props to potentially optimize performance, especially if this component is rendered frequently:

const memoizedTransaction = useMemo(
  () => (
    <Transaction
      transaction={item}
      // ... other props ...
      categories={categories}
      onCheckTransaction={onCheckTransaction}
      reconcile={reconcile}
    />
  ),
  [item, categories, onCheckTransaction, reconcile]
);

Then use memoizedTransaction in place of the inline Transaction component. This could help prevent unnecessary re-renders if the props remain the same.


Line range hint 1-1048: Overall assessment: Improved category management and component structure

The changes made to the ImportTransactionsModal component significantly improve its structure and data management, particularly in handling categories. The introduction of the useCategories hook and consistent updates throughout the component enhance data consistency and potentially improve performance.

Key improvements:

  1. Centralized category management with useCategories hook
  2. Simplified and more flexible parseCategoryFields function
  3. Consistent updates in getImportPreview, onImport, and the render method

These changes align well with React best practices and are likely to improve the overall maintainability of the component. The suggestions for further optimization (like memoization) could be considered in future iterations to potentially enhance performance, especially if this component handles large datasets or is rendered frequently.

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

819-819: Consider making isLoading optional in SelectedItemsButtonProps

The isLoading prop has been added as a required property in the SelectedItemsButtonProps interface. However, since it has a default value in the component implementation, it would be more flexible to make it optional in the type definition.

Consider updating the type definition as follows:

type SelectedItemsButtonProps<Name extends string> = {
  id: string;
  name: ((count: number) => string) | string;
  items: MenuItem<Name>[];
  onSelect: (name: Name, items: string[]) => void;
-  isLoading: boolean;
+  isLoading?: boolean;
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8ed0a14 and 5ca076c.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (22)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/desktop-client/src/components/ManageRules.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/schedules/index.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (2)
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
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.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
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.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (32)
packages/loot-core/src/client/data-hooks/schedules.tsx (3)

7-8: Improved type safety and alignment with other file changes.

The addition of new imports and type definitions enhances type safety and aligns with changes in other parts of the codebase. The switch from getAccountFilter to accountFilter maintains consistency with updates in the queries.ts file.

Also applies to: 15-20


69-126: Improved state management and effect cleanup in useSchedules hook.

The changes to the useSchedules hook significantly improve state management and align with React best practices. The use of useRef for query references and enhanced effect cleanup prevent potential memory leaks.

Regarding the past review comment about potential state updates after unmount, the current implementation already addresses this concern. The isUnmounted flag is used to prevent state updates after the component unmounts:

if (!isUnmounted) {
  setIsLoading(false);
  setData({ schedules, statuses });
}

This implementation effectively prevents state updates after the component unmounts, resolving the issue raised in the previous review.


Line range hint 131-149: Improved flexibility and consistency in SchedulesProvider and SchedulesContext.

The changes to SchedulesProvider and SchedulesContext improve flexibility in query construction and ensure consistency with the useSchedules hook's return value. These updates align well with the overall refactoring of the component.

packages/desktop-client/src/components/accounts/Balance.jsx (5)

1-1: LGTM: Import statements cleaned up and updated.

The changes in the import statements improve code cleanliness by removing unused imports and adding necessary ones for the updated component implementation. This addresses the previous review comments about unused imports.

Also applies to: 4-4, 14-14


90-93: LGTM: Improved schedule balance calculation logic.

The updated condition for calculating scheduleBalance now correctly uses accountId for comparison, which is more precise and consistent with the overall changes in the component.


117-117: LGTM: FilteredBalance function improved with better naming and default value handling.

The changes enhance the function by using a more descriptive parameter name 'filteredBalance' and ensuring a valid number is always passed to DetailedBalance with 'filteredBalance || 0'.

Also applies to: 122-122


128-128: LGTM: MoreBalances function updated to use accountId for more specific querying.

The changes improve the function by incorporating 'accountId' in both the function parameters and the useSheetValue hook names. This allows for more precise querying and better cache management.

Also applies to: 132-133, 136-137


158-165: LGTM: Improved performance with memoized balanceBinding.

The introduction of useMemo for balanceBinding is a good optimization. It prevents unnecessary recalculations when other props change, potentially improving the component's performance.

packages/loot-core/src/client/data-hooks/transactions.ts (2)

194-222: Approve implementation of useTransactionsSearch hook.

The useTransactionsSearch hook is well-implemented and provides an efficient way to handle transaction searches.

Key points:

  1. The use of useMemo with debounce is an excellent optimization, preventing unnecessary function recreations and providing a smooth user experience.
  2. The search function correctly handles empty input by resetting the query and updating the searching state.
  3. The hook properly updates the query using the provided updateQuery function when a valid search text is entered.
  4. The returned object provides the necessary search function and isSearching state, allowing components to easily integrate search functionality.

The implementation is clean, efficient, and follows React best practices.


224-230: Approve implementation of isForPreview utility function.

The isForPreview function is concise and correctly implements the logic for determining whether a schedule should be included in the preview.

The function checks for two conditions:

  1. The schedule is not completed (!schedule.completed).
  2. The schedule status is either 'due', 'upcoming', or 'missed'.

This implementation effectively filters the schedules for preview purposes, ensuring that only relevant schedules are included.

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

21-28: Improved data management and type safety

The changes in imports and component structure show a positive shift towards using custom hooks for managing schedules and transactions. The use of accountSchedulesQuery and the updated SchedulesProvider props suggest a more efficient and type-safe approach to data fetching. The expanded type definition for accountId in the AccountTransactions component props enhances type safety and flexibility.

These changes align well with modern React practices and should improve the overall maintainability of the component.

Also applies to: 59-62, 81-86


283-305: Improved transaction handling logic

The refactored onOpenTransaction function now efficiently handles both regular and preview transactions. The use of isPreviewId to differentiate between transaction types is a clean approach. For preview transactions, dispatching a modal for scheduled transaction actions provides a good user experience.

This unified approach to handling different transaction types improves code organization and maintainability.


313-324: Improved TransactionListWithBalances usage

The updates to the TransactionListWithBalances component usage show good improvements in handling loading states and transaction data. Combining the loading states for both regular and preview transactions provides a more accurate representation of the overall loading status.

These changes simplify the component's props and improve the overall user experience by providing more accurate loading indicators.

As mentioned earlier, there's a typo in the variable name transcationsToDisplay. It should be transactionsToDisplay.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4)

2-8: Improved type safety and performance optimization preparation

The changes to imports and type definitions are well-considered:

  1. Adding useCallback prepares for performance optimizations.
  2. Including CSSProperties improves type checking for style objects.
  3. The new isLoading prop in SchedulesTableProps allows for better loading state management.
  4. Making schedules readonly in SchedulesTableProps enforces immutability, which is a good practice for React props.

These updates enhance type safety and set the stage for better performance. Good job!

Also applies to: 39-40


Line range hint 261-276: Consistent use of readonly for immutability

The items variable is now correctly typed as readonly SchedulesTableItem[], which is consistent with the earlier change to the schedules prop type. This enforces immutability throughout the component, which is a good practice in React for preventing accidental mutations.

The logic for filtering and handling completed schedules remains intact, ensuring that the functionality is preserved while improving type safety.


345-375: Consistent use of useCallback and clear item rendering logic

The new renderItem function is well-implemented:

  1. It uses useCallback for memoization, consistent with the renderSchedule function.
  2. The logic for handling different item types (completed schedules vs. regular items) is clear and concise.
  3. The function effectively delegates to renderSchedule for regular schedule items.

This implementation maintains consistency in the codebase and provides a clear separation of concerns for rendering different item types. Good job!


Line range hint 1-415: Overall assessment: Excellent improvements to SchedulesTable component

The changes made to the SchedulesTable component are commendable:

  1. Enhanced type safety with readonly types and better prop definitions.
  2. Improved performance through the use of useCallback and useMemo.
  3. Better code organization and readability with the refactored renderSchedule and renderItem functions.
  4. Improved user experience with the addition of loading state handling.

These changes align well with modern React practices and TypeScript best practices. The component is now more maintainable, performant, and type-safe. Great job on this refactoring!

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/desktop-client/src/components/accounts/Header.tsx (9)

11-11: New import added for Query type

The Query type is now imported from 'loot-core/shared/query'. This suggests that the component is now using a more specific query type, which could improve type safety and consistency across the application.


56-57: Updates to AccountHeaderProps type

Several changes have been made to the AccountHeaderProps type:

  1. isLoading property added
  2. accountId property added
  3. transactions changed to readonly array
  4. balanceQuery and filteredQuery properties updated/added
  5. showFilteredBalance and filteredBalance properties added

These changes suggest improvements in type safety and the addition of new functionality related to filtered balances and loading states.

Also applies to: 65-65, 71-72, 75-76


133-134: Updates to AccountHeader component props

The AccountHeader component now accepts new props such as isLoading, accountId, showFilteredBalance, and filteredBalance. Some props like transactionsQuery and canCalculateBalance have been commented out.

These changes align with the updates made to the AccountHeaderProps type and suggest a shift in how data is being passed and managed within the component.

Also applies to: 148-153, 155-155


199-199: Search state management updated

The component now manages its own search state using useState hook. The onChange handler for the Search component has been updated to set both the local state and call the onSearch prop.

This change allows for more flexible search functionality, potentially enabling immediate local filtering while still allowing parent components to react to search changes.

Also applies to: 355-358


361-376: Updates to SelectedTransactionsButton component

The SelectedTransactionsButton component now receives several new props, including isLoading and various callback functions. The getTransaction prop now uses the readonly transactions array.

These changes improve the component's functionality by adding loading state handling and providing more granular control over various actions related to selected transactions.


378-405: New Reconcile button and menu added

A new Reconcile button and associated popover menu have been added for account reconciliation. This feature allows users to initiate the reconciliation process for the account.

The implementation looks good, using the Popover component for the menu and managing its open state with useState.


513-513: Update to ReconcilingMessage component props

The ReconcilingMessage component now receives accountId as a prop. This change aligns with the addition of accountId to the AccountHeaderProps and suggests that the reconciliation process now requires the account ID.


296-300: Updates to Balances component props

The Balances component now receives accountId, filteredBalance, and showFilteredBalance as props. The isLoading prop has been commented out.

These changes suggest that the Balances component has been updated to handle filtered balance display, which aligns with the new properties added to AccountHeaderProps.

To ensure these prop changes are consistent with the Balances component implementation, let's verify its definition:

✅ Verification successful

Verification Complete: Balances Component Props Updated Correctly

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify Balances component props
rg "export function Balances\(" packages/desktop-client/src/components/accounts/Balance.tsx -A 10

Length of output: 199


Script:

#!/bin/bash
# Search for Balances.tsx in the components directory
fd "Balances.tsx" packages/desktop-client/src/components/

# If not found, search for the Balances component export
ast-grep --pattern $'export function Balances($_) { $$$ }' packages/desktop-client/src/components/

Length of output: 7861


677-677: Removal of canShowBalances functionality

The canShowBalances prop and related functionality have been commented out in the AccountMenu component. The menu item for toggling balance visibility is now always included, regardless of any condition.

This change suggests that the ability to show/hide balances is now always available to users. Ensure that this aligns with the intended user experience and that there are no scenarios where hiding this option would be necessary.

Let's verify if there are any remaining checks for canShowBalances in the codebase:

Also applies to: 699-699, 722-737

✅ Verification successful

Verification Successful

No active checks for canShowBalances found in the codebase. All instances are commented out in packages/desktop-client/src/components/accounts/Header.tsx.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify if there are any remaining checks for canShowBalances in the codebase
rg "canShowBalances" --type ts --type tsx

Length of output: 69


Script:

#!/bin/bash
# Re-verify if there are any remaining checks for canShowBalances in .ts and .tsx files
rg "canShowBalances" -g "*.ts" -g "*.tsx"

Length of output: 422

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

8-8: Improved category management with useCategories hook

The addition of the useCategories hook and its usage in the component is a positive change. This centralized approach to category management can lead to better data consistency and reusability across the application.

Also applies to: 158-159


587-588: Updated onImport function for new category structure

The changes in the onImport function correctly adapt to the new category management approach. This ensures consistency in how categories are handled during the import process.

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

827-827: LGTM: isLoading prop implementation in SelectedItemsButton

The isLoading prop has been correctly added to the SelectedItemsButton component with a default value of false. The ButtonWithLoading component is now used instead of a regular button, which is a good practice for showing loading states.

Also applies to: 842-856

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)

769-776: Efficient handling of loading state in PayeeIcons

The added check for isLoading ensures that PayeeIcons does not attempt to access schedules before they are loaded, preventing potential runtime errors due to accessing undefined data. This change enhances the reliability of the component during data fetching.


Line range hint 1765-1775: Properly passing isLoading prop to TransactionTableInner

Adding isLoading as a prop to TransactionTableInner allows the component to manage and reflect the loading state of transactions effectively. This change facilitates better user experience by enabling loading indicators or placeholders when data is being fetched.


1994-1994: Passing isLoading to the Table component to display loading state

By passing loading={isLoading} to the Table component, the table now correctly reflects the loading state of the data. This ensures that the user interface provides appropriate feedback during data fetch operations, enhancing usability.

Comment on lines 32 to 97
export function useTransactions({
query,
options = { pageCount: 50 },
}: UseTransactionsProps): UseTransactionsResult {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | undefined>(undefined);
const [transactions, setTransactions] = useState<
ReadonlyArray<TransactionEntity>
>([]);

const pagedQueryRef = useRef<PagedQuery<TransactionEntity> | null>(null);

// We don't want to re-render if options changes.
// Putting options in a ref will prevent that and
// allow us to use the latest options on next render.
const optionsRef = useRef(options);
optionsRef.current = options;

useEffect(() => {
let isUnmounted = false;

setError(undefined);
setIsLoading(query !== null);

if (!query) {
return;
}

if (query.state.table !== 'transactions') {
setError(new Error('Query must be a transactions query.'));
return;
}

pagedQueryRef.current = pagedQuery<TransactionEntity>(query, {
onData: data => {
if (!isUnmounted) {
setTransactions(data);
setIsLoading(false);
}
},
onError: setError,
options: { pageCount: optionsRef.current.pageCount },
});

return () => {
isUnmounted = true;
pagedQueryRef.current?.unsubscribe();
};
}, [query]);

return {
transactions,
isLoading,
error,
reload: pagedQueryRef.current?.run,
loadMore: pagedQueryRef.current?.fetchNext,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Approve implementation with a minor suggestion for loading state.

The useTransactions hook implementation is well-structured and optimized. The use of refs for options and paged query is a good approach to prevent unnecessary re-renders. The error handling and cleanup in the effect are correctly implemented.

However, there's a minor issue with how the loading state is set:

On line 54, the condition query !== null doesn't account for undefined. Consider using !!query instead:

- setIsLoading(query !== null);
+ setIsLoading(!!query);

This change ensures that isLoading is set correctly for both null and undefined query values.

The rest of the implementation, including the paged query setup, error handling, and the returned object, is well-implemented and provides the necessary functionality for managing transactions data.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function useTransactions({
query,
options = { pageCount: 50 },
}: UseTransactionsProps): UseTransactionsResult {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | undefined>(undefined);
const [transactions, setTransactions] = useState<
ReadonlyArray<TransactionEntity>
>([]);
const pagedQueryRef = useRef<PagedQuery<TransactionEntity> | null>(null);
// We don't want to re-render if options changes.
// Putting options in a ref will prevent that and
// allow us to use the latest options on next render.
const optionsRef = useRef(options);
optionsRef.current = options;
useEffect(() => {
let isUnmounted = false;
setError(undefined);
setIsLoading(query !== null);
if (!query) {
return;
}
if (query.state.table !== 'transactions') {
setError(new Error('Query must be a transactions query.'));
return;
}
pagedQueryRef.current = pagedQuery<TransactionEntity>(query, {
onData: data => {
if (!isUnmounted) {
setTransactions(data);
setIsLoading(false);
}
},
onError: setError,
options: { pageCount: optionsRef.current.pageCount },
});
return () => {
isUnmounted = true;
pagedQueryRef.current?.unsubscribe();
};
}, [query]);
return {
transactions,
isLoading,
error,
reload: pagedQueryRef.current?.run,
loadMore: pagedQueryRef.current?.fetchNext,
};
}
export function useTransactions({
query,
options = { pageCount: 50 },
}: UseTransactionsProps): UseTransactionsResult {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | undefined>(undefined);
const [transactions, setTransactions] = useState<
ReadonlyArray<TransactionEntity>
>([]);
const pagedQueryRef = useRef<PagedQuery<TransactionEntity> | null>(null);
// We don't want to re-render if options changes.
// Putting options in a ref will prevent that and
// allow us to use the latest options on next render.
const optionsRef = useRef(options);
optionsRef.current = options;
useEffect(() => {
let isUnmounted = false;
setError(undefined);
setIsLoading(!!query);
if (!query) {
return;
}
if (query.state.table !== 'transactions') {
setError(new Error('Query must be a transactions query.'));
return;
}
pagedQueryRef.current = pagedQuery<TransactionEntity>(query, {
onData: data => {
if (!isUnmounted) {
setTransactions(data);
setIsLoading(false);
}
},
onError: setError,
options: { pageCount: optionsRef.current.pageCount },
});
return () => {
isUnmounted = true;
pagedQueryRef.current?.unsubscribe();
};
}, [query]);
return {
transactions,
isLoading,
error,
reload: pagedQueryRef.current?.run,
loadMore: pagedQueryRef.current?.fetchNext,
};
}

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: 5

🧹 Outside diff range and nitpick comments (12)
packages/loot-core/src/client/data-hooks/filters.ts (1)

23-25: LGTM: Migrated to useQuery hook

The migration from useLiveQuery to useQuery is consistent with the PR objectives and improves data fetching across the codebase.

However, consider adding a comment explaining why an empty array is used as the second argument to useQuery. This would clarify that the query has no dependencies and will only run once when the component mounts.

packages/loot-core/src/client/data-hooks/schedules.tsx (3)

26-50: Enhanced error handling and data processing

The updates to loadStatuses function improve error handling and data processing flexibility. The use of readonly type and Map aligns well with immutability practices.

Consider wrapping the onData callback in a try-catch block to handle any potential errors during the status calculation:

onData: data => {
  try {
    // ... existing code ...
    onData?.(scheduleStatuses);
  } catch (error) {
    onError(error instanceof Error ? error : new Error(String(error)));
  }
},

This ensures that any unexpected errors during status calculation are properly caught and propagated.


54-78: Improved type definitions and state management

The new type definitions and changes to the useSchedules hook significantly enhance type safety, readability, and state management. The use of useRef for live queries is a good practice for managing subscriptions across renders.

Consider using a discriminated union type for UseSchedulesResult to make the loading and error states more explicit:

type UseSchedulesResult =
  | { readonly isLoading: true; readonly error?: undefined; schedules: []; statuses: Map<string, never> }
  | { readonly isLoading: false; readonly error: Error; schedules: []; statuses: Map<string, never> }
  | { readonly isLoading: false; readonly error?: undefined } & ScheduleData;

This approach can provide more precise type information based on the current state of the hook.


151-173: Well-structured query builder function

The new accountSchedulesQuery function is a valuable addition, providing a centralized and flexible way to construct queries for account schedules. The use of accountFilter and the special handling for 'uncategorized' accounts demonstrate thoughtful query construction.

To further enhance type safety, consider using a union type for the accountId parameter:

type AccountIdParam = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized';

export function accountSchedulesQuery(accountId?: AccountIdParam) {
  // ... existing code ...
}

This change would make the allowed values for accountId more explicit and catch potential typos at compile-time.

packages/desktop-client/src/components/accounts/Balance.jsx (5)

45-45: LGTM: SelectedBalance function improved with better parameter naming and loading state handling.

The changes in the SelectedBalance function look good. The parameter change from 'account' to 'accountId' improves consistency. The addition of loading state handling for schedules enhances the component's robustness.

Consider renaming the destructured variable isLoading to something more specific, like isSchedulesLoading, to improve clarity:

-  const { isLoading, schedules = [] } = useCachedSchedules();
+  const { isLoading: isSchedulesLoading, schedules = [] } = useCachedSchedules();

-  if (isLoading) {
+  if (isSchedulesLoading) {
     return null;
   }

Also applies to: 72-76


149-152: LGTM: Balances function signature updated with new parameters.

The Balances function signature has been appropriately updated with new parameters that reflect changes in how balance data is passed and managed. The addition of showFilteredBalance suggests new functionality for displaying filtered balances.

Consider adding JSDoc comments to document the purpose and expected types of the new parameters, especially balanceQuery and showFilteredBalance. This will improve code maintainability and help other developers understand the component's API.


157-165: LGTM: balanceBinding logic improved with useMemo.

The changes to the balanceBinding logic look good. The use of useMemo can improve performance by memoizing the value, and the dependencies are correctly set to accountId and balanceQuery.

Consider adjusting the formatting of the useMemo hook for better readability:

const balanceBinding = useMemo(
  () => ({
    name: `balance-query-${accountId}`,
    query: balanceQuery,
    value: 0,
  }),
  [accountId, balanceQuery]
);

Line range hint 176-216: LGTM: ButtonWithLoading component updated with improved styling and loading state handling.

The changes to the button component look good. The switch to ButtonWithLoading and the addition of the isLoading prop improve the handling of loading states. The use of @emotion/css for styling is consistent with modern practices and the import changes.

Consider extracting the button styles into a separate constant or styled component to improve code organization and reusability. For example:

const buttonStyles = css`
  padding-top: 1px;
  padding-bottom: 1px;
  & svg {
    width: 10px;
    height: 10px;
    margin-left: 10px;
    color: ${theme.pillText};
    transform: ${props => props.showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)'};
    opacity: ${props => props.selectedItemsSize > 0 || props.showExtraBalances ? 1 : 0};
  }
  &[data-hovered] svg {
    opacity: 1;
  }
`;

// Then use it in the component:
<ButtonWithLoading
  // ... other props
  className={buttonStyles}
>
  {/* ... button content */}
</ButtonWithLoading>

217-223: LGTM: MoreBalances component usage updated correctly.

The changes to the MoreBalances component usage look good. The addition of the accountId prop and the balanceQuery prop align with the updates to the MoreBalances function signature.

Remove the commented-out transactionsQuery prop to keep the code clean:

  <MoreBalances
    accountId={accountId}
-   // transactionsQuery={transactionsQuery}
    balanceQuery={balanceQuery}
  />
packages/loot-core/src/client/data-hooks/transactions.ts (1)

95-186: LGTM: Well-implemented usePreviewTransactions hook with room for optimization

The usePreviewTransactions hook is well-implemented with proper error handling, loading state management, and effect cleanup. The use of useMemo for scheduleTransactions is a good optimization.

Consider memoizing the Promise.all callback to prevent unnecessary recreations on each render:

const applyRulesToTransactions = useCallback((transactions) => {
  return Promise.all(
    transactions.map(transaction =>
      send('rules-run', { transaction })
    )
  );
}, []);

// Use applyRulesToTransactions in the effect
applyRulesToTransactions(scheduleTransactions)
  .then(newTrans => {
    // ... existing logic
  })

This optimization could potentially improve performance, especially if the component re-renders frequently.

packages/desktop-client/src/components/transactions/TransactionList.jsx (1)

Line range hint 107-119: LGTM: Improved handling of date changes in onSave

The modifications to the onSave function improve the handling of date changes in transactions. By updating the sort_order and triggering a refetch when the date changes, you ensure that the transaction list remains properly sorted and up-to-date.

Consider adding a comment explaining why Date.now() is used for sort_order, as it might not be immediately obvious to other developers.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (1)

285-307: Dependencies in onOpenTransaction callback

In the onOpenTransaction callback, you might want to include isPreviewId in the dependency array of useCallback. Although it's a utility function, including it ensures that all dependencies are accounted for, especially if isPreviewId ever changes.

Suggestion:

}, [
  dispatch,
  navigate,
+ isPreviewId,
]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 5ca076c and d0640a7.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (24)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
  • packages/desktop-client/src/components/ManageRules.tsx
  • packages/desktop-client/src/components/accounts/Header.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/schedules/index.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (3)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (2)
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
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.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
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.
packages/loot-core/src/client/data-hooks/filters.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/loot-core/src/client/query-helpers.ts

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🔇 Additional comments (48)
packages/loot-core/src/client/data-hooks/filters.ts (4)

2-2: LGTM: Import changes align with new hook usage

The import changes are consistent with the migration to functional components and the use of the new useQuery hook. This aligns well with the PR objectives of simplifying transaction fetching across the codebase.

Also applies to: 6-6


8-8: LGTM: Improved type safety with return type annotation

The addition of the TransactionFilterEntity[] return type to the toJS function enhances type safety and improves code readability. This is a good TypeScript practice.


29-35: LGTM: Efficient sorting implementation with useCallback

The new sort function implementation using useCallback is a good approach for memoization. The sorting logic is correct and handles edge cases well.

Regarding the previous comment about using toSorted, the current implementation with [...filters].sort() is actually preferable. It creates a new array before sorting, which is a good practice to avoid mutating the original array, and it has broader browser compatibility than toSorted.


37-37: LGTM: Efficient memoization of sorted filters

The use of useMemo with the correct dependencies (data and sort) ensures that the filters are only re-sorted when necessary. This implementation aligns well with the learning from PR #3685 about avoiding unnecessary rerenders when options change.

The spread operator [...data] is used correctly to create a new array, maintaining immutability.

packages/loot-core/src/client/data-hooks/schedules.tsx (4)

7-20: Improved type safety and module organization

The addition of new imports, especially the explicit types from the models, enhances type safety throughout the file. The change in the accountFilter import location suggests a positive refactoring of the queries module, improving code organization.


80-117: Robust error handling and resource management

The changes in the useEffect hook significantly improve error handling, query validation, and resource management. The isUnmounted flag effectively addresses the concern raised in the previous review about preventing state updates after component unmount.

The cleanup function now properly unsubscribes from both queries, preventing potential memory leaks. The query validation ensures that the hook is used correctly with schedules queries only.

These improvements align well with React best practices and enhance the overall reliability of the component.


128-140: Improved context and provider implementation

The updates to SchedulesContext and SchedulesProvider align well with the changes made to the useSchedules hook. Including isLoading and error states in the context provides consumers with more comprehensive information about the current state of the schedules data.

The change from a transform prop to a query prop in SchedulesProvider offers more flexibility in how queries are constructed and passed down the component tree. This change is consistent with the broader refactoring efforts seen in the file.


Line range hint 1-173: Comprehensive improvements enhancing reliability and maintainability

This refactoring of the schedules module introduces significant improvements across multiple areas:

  1. Enhanced type safety through explicit type definitions and stricter typing.
  2. Improved error handling and state management in the useSchedules hook.
  3. Better resource management with proper cleanup of subscriptions.
  4. More flexible query construction with the new accountSchedulesQuery function.
  5. Updated context and provider implementations that align with the new hook structure.

These changes collectively result in a more robust, maintainable, and flexible implementation of the schedules functionality. The refactoring aligns well with React best practices and addresses previous concerns while introducing valuable new features.

The migration to a functional component approach and the use of the new useTransactions hook (as mentioned in the PR objectives) is well-executed in this file. The changes contribute positively to the overall goal of simplifying transaction fetching across the codebase.

packages/desktop-client/src/components/accounts/Balance.jsx (5)

1-1: LGTM: Import statements cleaned up and updated.

The changes in the import statements look good. Unused hooks have been removed, addressing the previous comment about unused imports. The addition of css from @emotion/css and the change to ButtonWithLoading suggest improvements in styling and button functionality.

Also applies to: 4-4, 14-14


Line range hint 90-94: LGTM: Balance calculation logic updated correctly.

The changes in the balance calculation logic correctly reflect the switch from 'account' to 'accountId'. The condition and subsequent calculations have been appropriately updated.


117-117: LGTM: FilteredBalance function parameter renamed for clarity.

The change from 'filteredAmount' to 'filteredBalance' in the FilteredBalance function improves clarity and consistency. The usage of the parameter has been correctly updated in the component.

Also applies to: 122-122


128-128: LGTM: MoreBalances function updated with accountId for improved specificity.

The MoreBalances function has been correctly updated to include accountId in its signature and query names. This change improves the specificity of the queries and aligns with the overall use of accountId throughout the component.

Also applies to: 132-133, 136-137


225-228: LGTM: SelectedBalance and FilteredBalance usage updated correctly.

The changes to the SelectedBalance and FilteredBalance component usage look good. The SelectedBalance now correctly uses the accountId prop, and the FilteredBalance is conditionally rendered with the filteredBalance prop. These changes align with the updates to their respective function signatures and add flexibility to the component.

packages/loot-core/src/client/data-hooks/transactions.ts (4)

1-30: LGTM: Imports and type definitions are well-structured

The imports and type definitions for the useTransactions hook are well-organized and provide a clear interface for the hook's props and results.


32-93: LGTM: Well-implemented useTransactions hook with optimizations

The useTransactions hook is well-implemented, following React best practices. Notable optimizations include:

  • Using refs for options and paged query to prevent unnecessary re-renders.
  • Proper error handling and cleanup in the effect.
  • Correct loading state management using !!query.

These optimizations align with the learning from PR #3685 about avoiding unnecessary rerenders when options change.


188-236: LGTM: Well-implemented useTransactionsSearch hook and helper function

The useTransactionsSearch hook is concise and follows React best practices. The use of debounce for search input changes is appropriate and will help prevent unnecessary API calls. The isForPreview helper function is simple and correctly implemented.


1-236: Overall: Excellent implementation of transaction-related hooks

This file demonstrates a high-quality implementation of React hooks for managing transactions. Key strengths include:

  1. Well-structured type definitions for clear interfaces.
  2. Optimized implementations using refs and memoization to prevent unnecessary re-renders.
  3. Proper error handling and loading state management across all hooks.
  4. Effective use of debounce for search functionality.

The code is clean, follows React best practices, and shows attention to performance optimization. The suggested minor optimization for usePreviewTransactions could further improve the already solid implementation.

packages/desktop-client/src/components/transactions/TransactionList.jsx (3)

60-60: LGTM: Addition of isLoading prop

The introduction of the isLoading prop with a default value of false is a good addition. This will allow for better management of loading states in the component, potentially improving the user experience.


200-200: LGTM: Propagation of isLoading prop to TransactionTable

Passing the isLoading prop to the TransactionTable component is a good practice. This allows the loading state to be propagated down the component tree, enabling the TransactionTable to respond appropriately to loading states.


Line range hint 1-240: Overall assessment: Improvements to transaction handling and loading state management

The changes in this file align well with the PR objectives of migrating to functional components and improving state management. The modifications to the onSave function enhance the handling of date changes in transactions, ensuring proper sorting and UI updates. The introduction of the isLoading prop and its propagation to child components improves the management of loading states throughout the transaction-related components.

These changes contribute positively to the codebase by improving user experience and maintaining data consistency. The code is well-structured and the modifications are thoughtful and purposeful.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6)

2-8: LGTM! Improved type definitions and performance optimizations.

The changes in imports and type definitions are well-considered:

  1. Adding useCallback paves the way for performance optimizations.
  2. The new isLoading prop in SchedulesTableProps aligns with the PR objective of simplifying transaction fetching.
  3. Changing schedules to readonly ScheduleEntity[] enforces immutability, which is a good practice for preventing unintended mutations.

These updates enhance type safety and set the stage for performance improvements.

Also applies to: 39-40


Line range hint 261-276: LGTM! Consistent use of readonly type.

The change to readonly SchedulesTableItem[] for the items variable is consistent with the earlier modification to the schedules prop. This maintains immutability throughout the component, which is a good practice for preventing unintended mutations and potential bugs.

The existing logic for handling completed schedules is preserved, ensuring that the component's functionality remains intact.


278-343: LGTM! Performance optimization with useCallback.

The refactoring of renderSchedule to use useCallback is a good performance optimization:

  1. It memoizes the function, preventing unnecessary re-renders of child components that depend on this function.
  2. The dependencies array is correctly specified, ensuring the function is only recreated when necessary.

The core logic of the function remains unchanged, preserving its functionality while improving performance. The minor style adjustments (lines 288-289) are appropriate and don't affect the component's behavior.

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


345-375: LGTM! Consistent performance optimization.

The refactoring of renderItem to use useCallback is consistent with the earlier optimization of renderSchedule:

  1. It further enhances performance by memoizing this function as well.
  2. The dependency array correctly includes only renderSchedule, as it's the only external dependency used in the function.

This change maintains the component's functionality while improving its overall performance characteristics.


Line range hint 406-412: LGTM! Improved loading state handling.

The addition of the loading prop to the Table component is a valuable improvement:

  1. It aligns with the new isLoading prop added to SchedulesTableProps.
  2. This change allows the Table component to handle loading states, which can significantly improve user experience by providing visual feedback during data fetching.

This modification directly supports the PR objective of simplifying transaction fetching across the codebase.


Line range hint 1-415: Overall, excellent improvements to SchedulesTable component!

This refactoring successfully achieves the PR objectives:

  1. The component has been effectively migrated to a functional component.
  2. Performance optimizations have been implemented through the use of useCallback.
  3. Type safety has been improved with the addition of readonly modifiers and the new isLoading prop.
  4. The changes support better handling of loading states, which aligns with the goal of simplifying transaction fetching.

These modifications enhance the component's performance, maintainability, and user experience. Great job on this refactoring!

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/loot-core/src/client/query-helpers.ts (5)

4-4: Improved type safety and readability with new imports and type definitions.

The addition of the Query type import and the new type definitions (Data, Listener, LiveQueryOptions, and PagedQueryOptions) significantly enhance the overall type safety and readability of the code. These changes provide better structure and reusability across the file.

Also applies to: 48-57, 242-244


10-22: Enhanced liveQuery function with improved type safety and flexibility.

The liveQuery function has been significantly improved:

  • Use of generics (<TResponse>) allows for flexible response types.
  • Structured options object enhances readability and maintainability.
  • Clear return type LiveQuery<TResponse> improves type safety.

These changes make the function more robust and easier to use correctly.


25-45: Consistent improvements in pagedQuery function.

The pagedQuery function has been updated consistently with liveQuery:

  • Use of generics for flexible response types.
  • Structured options object for better readability.
  • Clear return type PagedQuery<TResponse>.

This consistency in approach across related functions improves the overall coherence of the API.


Line range hint 247-426: Enhanced PagedQuery class with improved pagination handling and type safety.

The PagedQuery class has been significantly improved:

  • Extends LiveQuery with generics for better type consistency.
  • Robust pagination handling with new properties and methods.
  • Refactored run, refetchUpToRow, and fetchNext methods for better efficiency.
  • Use of once for fetchNext prevents multiple simultaneous fetches, which is a good optimization.
  • optimisticUpdate now correctly updates the total count.

These enhancements make the pagination logic more reliable and efficient.

🧰 Tools
🪛 Biome

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)


Line range hint 1-426: Excellent refactoring with significant improvements in type safety and structure.

This refactoring represents a substantial improvement to the codebase:

  1. Consistent use of TypeScript generics and new type definitions enhance type safety throughout.
  2. Restructured function signatures with options objects improve API clarity and flexibility.
  3. Refactored class implementations, particularly in LiveQuery and PagedQuery, provide better separation of concerns and more robust functionality.
  4. Pagination handling in PagedQuery is now more efficient and reliable.

These changes align well with modern TypeScript best practices and should significantly improve code maintainability and reduce the likelihood of runtime errors. Great work on this refactoring effort!

🧰 Tools
🪛 Biome

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

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

8-8: New hook imported for category management.

The useCategories hook has been imported, indicating a shift towards a more centralized category management approach. This change aligns with best practices for state management in React components.


Line range hint 141-151: Updated category parsing logic.

The parseCategoryFields function has been modified to work with the new category structure. It now iterates through the categories to find a match based on either ID or name. This change improves the flexibility of category matching.


158-159: Improved prop handling and category management.

The component now directly destructures accountId and onImported from the options prop, simplifying the interface. The useCategories hook is used to obtain the list of categories, which centralizes category management and reduces prop drilling.


243-246: Enhanced category handling in import preview.

The getImportPreview function now uses the parseCategoryFields helper with the new category structure, improving consistency in category handling. The dependency array for useCallback has been correctly updated to include categories, ensuring the function is recreated when categories change.

Also applies to: 313-313


587-588: Consistent category handling in import process.

The onImport function now uses the parseCategoryFields helper with the new category structure, ensuring consistency with the preview functionality. This change maintains a uniform approach to category handling throughout the import process.


789-789: Updated category prop passing to Transaction component.

The categories prop is now directly passed to the Transaction component, reflecting the centralized category management approach. This change ensures that the child component has access to the most up-to-date category information.


Line range hint 1-1037: Overall improvement in category management and component structure.

The changes in this file represent a significant improvement in how categories are managed within the ImportTransactionsModal component. By introducing the useCategories hook and consistently updating category handling throughout the component, the code now follows a more centralized and efficient approach to category management. This refactoring enhances maintainability, reduces prop drilling, and ensures consistent category handling across different functions within the component.

Key improvements include:

  1. Centralized category management using the useCategories hook.
  2. Consistent use of the parseCategoryFields helper function.
  3. Updated dependency arrays in useCallback hooks to properly react to category changes.
  4. Simplified prop handling and passing to child components.

These changes align well with React best practices and should improve the overall performance and maintainability of the component.

packages/desktop-client/src/components/table.tsx (4)

819-819: New prop isLoading added to SelectedItemsButtonProps

The isLoading prop has been added to the SelectedItemsButtonProps type. This is a good addition as it allows the component to handle loading states.


827-827: Default value for isLoading prop

The isLoading prop is given a default value of false in the function parameters. This is a good practice as it maintains backward compatibility with existing usage of the component.


Line range hint 842-856: ButtonWithLoading component used instead of Button

The Button component has been replaced with ButtonWithLoading, which now accepts the isLoading prop. This change allows the button to display a loading state when necessary.

A few observations:

  1. The isLoading prop is correctly passed to the ButtonWithLoading component.
  2. The ref is properly forwarded using triggerRef.
  3. The onPress handler now sets the menuOpen state directly, which is an improvement in terms of readability and performance compared to using a callback function.

These changes enhance the functionality of the component while maintaining its existing behavior.


Line range hint 819-856: Summary: Loading state support added to SelectedItemsButton

The changes introduce loading state support to the SelectedItemsButton component. This is achieved by:

  1. Adding an isLoading prop to SelectedItemsButtonProps.
  2. Using the ButtonWithLoading component instead of Button.
  3. Passing the isLoading prop to ButtonWithLoading.

These changes enhance the component's functionality and user experience by providing visual feedback during asynchronous operations. The implementation is clean, follows React best practices, and maintains backward compatibility.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6)

59-63: Efficient use of useMemo and updated SchedulesProvider

Great job refactoring to use useMemo for schedulesQuery. The dependency array is correctly specified with [accountId], ensuring the memoized value updates appropriately when accountId changes. Updating SchedulesProvider to accept the query prop aligns with the new API changes and enhances code readability.

Also applies to: 81-81


106-111: Proper implementation of useCallback with accurate dependencies

The callbacks onSave, onEditNotes, onCloseAccount, and onReopenAccount are correctly wrapped with useCallback. The dependency arrays include all necessary dependencies, which optimizes performance by preventing unnecessary re-renders.

Also applies to: 117-128, 130-136, 138-155


217-222: Expanded types for accountId and accountName

Modifying the types of accountId and accountName to include specific string literals enhances the flexibility of the component. This allows TransactionListWithPreviews to handle special account views like 'budgeted', 'offbudget', and 'uncategorized' seamlessly.


315-318: Variable transactionsToDisplay is correctly implemented

Good catch on correcting the variable name to transactionsToDisplay. This ensures consistency and readability in your code.


321-322: Proper loading states and transaction handling

Combining isLoading and isPreviewTransactionsLoading for the isLoading prop ensures that the loading state accurately reflects all data fetching. Passing transactionsToDisplay to the transactions prop correctly includes both preview and regular transactions when not searching.

Also applies to: 326-326


226-229: Ensure accountId is handled correctly when undefined

The baseTransactionsQuery function uses accountId, which might be undefined. Please verify that queries.transactions(accountId) can handle an undefined value without causing errors or unintended behavior.

You can run the following script to check how queries.transactions handles an undefined accountId:

This script searches for the transactions function definition to inspect how it handles accountId. Ensure that the function accounts for undefined values appropriately.

Comment on lines 325 to 327
{schedule._date && schedule._date.frequency && (
<SvgCheck style={{ width: 13, height: 13 }} />
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using optional chaining for improved readability.

As mentioned in previous review comments, you can simplify the condition using optional chaining. This change will make the code more concise without altering its behavior.

Apply this diff to simplify the condition:

-          {schedule._date && schedule._date.frequency && (
+          {schedule._date?.frequency && (
             <SvgCheck style={{ width: 13, height: 13 }} />
           )}

This change maintains the same logic while making the code more readable and concise.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{schedule._date && schedule._date.frequency && (
<SvgCheck style={{ width: 13, height: 13 }} />
)}
{schedule._date?.frequency && (
<SvgCheck style={{ width: 13, height: 13 }} />
)}
🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Comment on lines 235 to 238
} catch (e) {
console.log('Error fetching data', e);
this.onError(e);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider enhancing error handling and logging.

While the current error handling in fetchData is functional, it could be improved:

  1. Consider classifying errors (e.g., network errors, data errors) for more targeted handling.
  2. Log more details about the error, such as the query that caused it, to aid in debugging.
  3. Implement a retry mechanism for transient errors.

Here's a potential enhancement:

try {
  // ... existing code ...
} catch (e) {
  console.error('Error fetching data:', {
    error: e,
    query: this._query.serialize(),
    requestId: reqId
  });
  if (e instanceof NetworkError && this._retryCount < MAX_RETRIES) {
    this._retryCount++;
    await this.fetchData(runQuery);
  } else {
    this.onError(e);
  }
}

This suggestion adds more detailed logging, error classification, and a basic retry mechanism. Adjust according to your specific error types and retry policy.

Comment on lines 60 to 238
// to only react to remote syncs. By default, queries will
// always update to all changes.
//
// TODO: errors?
const syncTypes = this.onlySync ? ['success'] : ['applied', 'success'];

if (syncTypes.indexOf(type) !== -1) {
if (this._supportedSyncTypes.has(type)) {
this.onUpdate(tables);
}
});
}
};

unsubscribe = () => {
if (this._unsubscribe) {
this._unsubscribe();
this._unsubscribe = null;
if (this._unsubscribeSyncEvent) {
this._unsubscribeSyncEvent();
this._unsubscribeSyncEvent = null;
}
};

mapData = data => {
if (this.mapper) {
return this.mapper(data);
}
return data;
protected _optimisticUpdate = (
updateFn: (data: Data<TResponse>) => Data<TResponse>,
) => {
const previousData = this.data;
this.updateData(updateFn);
this.onData(this.data, previousData);
};

getQuery = () => {
return this.query;
optimisticUpdate = (dataFunc: (data: Data<TResponse>) => Data<TResponse>) => {
this._optimisticUpdate(dataFunc);
};

getData = () => {
return this.mappedData;
protected updateData = (
updateFn: (data: Data<TResponse>) => Data<TResponse>,
) => {
this.data = updateFn(this.data);
};

getNumListeners = () => {
return this.listeners.length;
};
protected fetchData = async (
runQuery: () => Promise<{
data: Data<TResponse>;
dependencies: Set<string>;
}>,
) => {
// TODO: precompile queries, or cache compilation results on the
// backend. could give a query an id which makes it cacheable via
// an LRU cache

isRunning = () => {
return this._unsubscribe != null;
};
const reqId = Math.random();
this.inflightRequestId = reqId;

_optimisticUpdate = (dataFunc, mappedDataFunc) => {
this.data = dataFunc(this.data);
this.mappedData = mappedDataFunc(this.mappedData);
};
try {
const { data, dependencies } = await runQuery();

optimisticUpdate = (dataFunc, mappedDataFunc) => {
const prevMappedData = this.mappedData;
this._optimisticUpdate(dataFunc, mappedDataFunc);
this.onData(this.mappedData, prevMappedData);
// Regardless if this request was cancelled or not, save the
// dependencies. The query can't change so all requests will
// return the same deps.
if (this._dependencies == null) {
this._dependencies = new Set(dependencies);
}

// Only fire events if this hasn't been cancelled and if we're
// still subscribed (`this.unsubscribeSyncEvent` will exist)
if (this.inflightRequestId === reqId && this._unsubscribeSyncEvent) {
const previousData = this.data;
this.data = data;
this.onData(this.data, previousData);
this.inflightRequestId = null;
}
} catch (e) {
console.log('Error fetching data', e);
this.onError(e);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Significant improvements in LiveQuery class structure and type safety.

The LiveQuery class has been substantially enhanced:

  • Use of generics improves type safety and flexibility.
  • Refactored fetchData method is more robust, handling cancellation and error cases effectively.
  • Clear separation of concerns with new private methods and properties.

These changes make the class more maintainable and less prone to errors.

However, there's a minor issue to address:

The type of _unsubscribeSyncEvent can be improved for clarity. Apply this change:

-private _unsubscribeSyncEvent: () => void | null;
+private _unsubscribeSyncEvent: (() => void) | null;

This change clarifies that _unsubscribeSyncEvent is either a function returning void or null, not a function returning void or null.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export class LiveQuery<TResponse = unknown> {
private _unsubscribeSyncEvent: () => void | null;
private _data: Data<TResponse>;
private _dependencies: Set<string>;
private _listeners: Array<Listener<TResponse>>;
private _supportedSyncTypes: Set<string>;
private _query: Query;
private _onError: (error: Error) => void;
get query() {
return this._query;
}
get data() {
return this._data;
}
private set data(data: Data<TResponse>) {
this._data = data;
}
get isRunning() {
return this._unsubscribeSyncEvent != null;
}
// Async coordination
inflight;
inflightRequestId;
restart;
private _inflightRequestId: number | null;
protected get inflightRequestId() {
return this._inflightRequestId;
}
private set inflightRequestId(id: number) {
this._inflightRequestId = id;
}
constructor(
query,
onData?: (response: Response) => void,
opts: { mapper?; onlySync?: boolean } = {},
query: Query,
onData: Listener<TResponse>,
onError?: (error: Error) => void,
options: LiveQueryOptions = {},
) {
this.error = new Error();
this.query = query;
this.data = null;
this.mappedData = null;
this.dependencies = null;
this.mapper = opts.mapper;
this.onlySync = opts.onlySync;
this.listeners = [];
// Async coordination
this.inflight = false;
this.restart = false;
this._query = query;
this._data = null;
this._dependencies = null;
this._listeners = [];
this._onError = onError || (() => {});
// TODO: error types?
this._supportedSyncTypes = options.onlySync
? new Set<string>(['success'])
: new Set<string>(['applied', 'success']);
if (onData) {
this.addListener(onData);
}
}
addListener(func) {
this.listeners.push(func);
addListener = (func: Listener<TResponse>) => {
this._listeners.push(func);
return () => {
this.listeners = this.listeners.filter(l => l !== func);
this._listeners = this._listeners.filter(l => l !== func);
};
}
};
onData = (data, prevData) => {
for (let i = 0; i < this.listeners.length; i++) {
this.listeners[i](data, prevData);
protected onData = (data: Data<TResponse>, prevData: Data<TResponse>) => {
for (let i = 0; i < this._listeners.length; i++) {
this._listeners[i](data, prevData);
}
};
onUpdate = tables => {
protected onError = (error: Error) => {
this._onError(error);
};
protected onUpdate = (tables: string[]) => {
// We might not know the dependencies if the first query result
// hasn't come back yet
if (
this.dependencies == null ||
tables.find(table => this.dependencies.has(table))
this._dependencies == null ||
tables.find(table => this._dependencies.has(table))
) {
this.run();
}
};
run = () => {
this.subscribe();
return this._fetchData(() => runQuery(this.query));
return this.fetchData(() => runQuery(this._query));
};
_fetchData = async makeRequest => {
// TODO: precompile queries, or cache compilation results on the
// backend. could give a query an id which makes it cacheable via
// an LRU cache
const reqId = Math.random();
this.inflightRequestId = reqId;
const { data, dependencies } = await makeRequest();
// Regardless if this request was cancelled or not, save the
// dependencies. The query can't change so all requests will
// return the same deps.
if (this.dependencies == null) {
this.dependencies = new Set(dependencies);
}
// Only fire events if this hasn't been cancelled and if we're
// still subscribed (`this._subscribe` will exist)
if (this.inflightRequestId === reqId && this._unsubscribe) {
const prevData = this.mappedData;
this.data = data;
this.mappedData = this.mapData(data);
this.onData(this.mappedData, prevData);
this.inflightRequestId = null;
}
static runLiveQuery = <TResponse>(
query: Query,
onData: Listener<TResponse>,
onError: (error: Error) => void,
options: LiveQueryOptions = {},
) => {
const liveQuery = new LiveQuery<TResponse>(query, onData, onError, options);
liveQuery.run();
return liveQuery;
};
subscribe = () => {
if (this._unsubscribe == null) {
this._unsubscribe = listen('sync-event', ({ type, tables }) => {
protected subscribe = () => {
if (this._unsubscribeSyncEvent == null) {
this._unsubscribeSyncEvent = listen('sync-event', ({ type, tables }) => {
// If the user is doing optimistic updates, they don't want to
// always refetch whenever something changes because it would
// refetch all data after they've already updated the UI. This
// voids the perf benefits of optimistic updates. Allow querys
// to only react to remote syncs. By default, queries will
// always update to all changes.
//
// TODO: errors?
const syncTypes = this.onlySync ? ['success'] : ['applied', 'success'];
if (syncTypes.indexOf(type) !== -1) {
if (this._supportedSyncTypes.has(type)) {
this.onUpdate(tables);
}
});
}
};
unsubscribe = () => {
if (this._unsubscribe) {
this._unsubscribe();
this._unsubscribe = null;
if (this._unsubscribeSyncEvent) {
this._unsubscribeSyncEvent();
this._unsubscribeSyncEvent = null;
}
};
mapData = data => {
if (this.mapper) {
return this.mapper(data);
}
return data;
protected _optimisticUpdate = (
updateFn: (data: Data<TResponse>) => Data<TResponse>,
) => {
const previousData = this.data;
this.updateData(updateFn);
this.onData(this.data, previousData);
};
getQuery = () => {
return this.query;
optimisticUpdate = (dataFunc: (data: Data<TResponse>) => Data<TResponse>) => {
this._optimisticUpdate(dataFunc);
};
getData = () => {
return this.mappedData;
protected updateData = (
updateFn: (data: Data<TResponse>) => Data<TResponse>,
) => {
this.data = updateFn(this.data);
};
getNumListeners = () => {
return this.listeners.length;
};
protected fetchData = async (
runQuery: () => Promise<{
data: Data<TResponse>;
dependencies: Set<string>;
}>,
) => {
// TODO: precompile queries, or cache compilation results on the
// backend. could give a query an id which makes it cacheable via
// an LRU cache
isRunning = () => {
return this._unsubscribe != null;
};
const reqId = Math.random();
this.inflightRequestId = reqId;
_optimisticUpdate = (dataFunc, mappedDataFunc) => {
this.data = dataFunc(this.data);
this.mappedData = mappedDataFunc(this.mappedData);
};
try {
const { data, dependencies } = await runQuery();
optimisticUpdate = (dataFunc, mappedDataFunc) => {
const prevMappedData = this.mappedData;
this._optimisticUpdate(dataFunc, mappedDataFunc);
this.onData(this.mappedData, prevMappedData);
// Regardless if this request was cancelled or not, save the
// dependencies. The query can't change so all requests will
// return the same deps.
if (this._dependencies == null) {
this._dependencies = new Set(dependencies);
}
// Only fire events if this hasn't been cancelled and if we're
// still subscribed (`this.unsubscribeSyncEvent` will exist)
if (this.inflightRequestId === reqId && this._unsubscribeSyncEvent) {
const previousData = this.data;
this.data = data;
this.onData(this.data, previousData);
this.inflightRequestId = null;
}
} catch (e) {
console.log('Error fetching data', e);
this.onError(e);
}
export class LiveQuery<TResponse = unknown> {
private _unsubscribeSyncEvent: (() => void) | null;
private _data: Data<TResponse>;
private _dependencies: Set<string>;
private _listeners: Array<Listener<TResponse>>;
private _supportedSyncTypes: Set<string>;
private _query: Query;
private _onError: (error: Error) => void;
get query() {
return this._query;
}
get data() {
return this._data;
}
private set data(data: Data<TResponse>) {
this._data = data;
}
get isRunning() {
return this._unsubscribeSyncEvent != null;
}
// Async coordination
private _inflightRequestId: number | null;
protected get inflightRequestId() {
return this._inflightRequestId;
}
private set inflightRequestId(id: number) {
this._inflightRequestId = id;
}
constructor(
query: Query,
onData: Listener<TResponse>,
onError?: (error: Error) => void,
options: LiveQueryOptions = {},
) {
this._query = query;
this._data = null;
this._dependencies = null;
this._listeners = [];
this._onError = onError || (() => {});
// TODO: error types?
this._supportedSyncTypes = options.onlySync
? new Set<string>(['success'])
: new Set<string>(['applied', 'success']);
if (onData) {
this.addListener(onData);
}
}
addListener = (func: Listener<TResponse>) => {
this._listeners.push(func);
return () => {
this._listeners = this._listeners.filter(l => l !== func);
};
};
protected onData = (data: Data<TResponse>, prevData: Data<TResponse>) => {
for (let i = 0; i < this._listeners.length; i++) {
this._listeners[i](data, prevData);
}
};
protected onError = (error: Error) => {
this._onError(error);
};
protected onUpdate = (tables: string[]) => {
// We might not know the dependencies if the first query result
// hasn't come back yet
if (
this._dependencies == null ||
tables.find(table => this._dependencies.has(table))
) {
this.run();
}
};
run = () => {
this.subscribe();
return this.fetchData(() => runQuery(this._query));
};
static runLiveQuery = <TResponse>(
query: Query,
onData: Listener<TResponse>,
onError: (error: Error) => void,
options: LiveQueryOptions = {},
) => {
const liveQuery = new LiveQuery<TResponse>(query, onData, onError, options);
liveQuery.run();
return liveQuery;
};
protected subscribe = () => {
if (this._unsubscribeSyncEvent == null) {
this._unsubscribeSyncEvent = listen('sync-event', ({ type, tables }) => {
// If the user is doing optimistic updates, they don't want to
// always refetch whenever something changes because it would
// refetch all data after they've already updated the UI. This
// voids the perf benefits of optimistic updates. Allow querys
// to only react to remote syncs. By default, queries will
// always update to all changes.
if (this._supportedSyncTypes.has(type)) {
this.onUpdate(tables);
}
});
}
};
unsubscribe = () => {
if (this._unsubscribeSyncEvent) {
this._unsubscribeSyncEvent();
this._unsubscribeSyncEvent = null;
}
};
protected _optimisticUpdate = (
updateFn: (data: Data<TResponse>) => Data<TResponse>,
) => {
const previousData = this.data;
this.updateData(updateFn);
this.onData(this.data, previousData);
};
optimisticUpdate = (dataFunc: (data: Data<TResponse>) => Data<TResponse>) => {
this._optimisticUpdate(dataFunc);
};
protected updateData = (
updateFn: (data: Data<TResponse>) => Data<TResponse>,
) => {
this.data = updateFn(this.data);
};
protected fetchData = async (
runQuery: () => Promise<{
data: Data<TResponse>;
dependencies: Set<string>;
}>,
) => {
// TODO: precompile queries, or cache compilation results on the
// backend. could give a query an id which makes it cacheable via
// an LRU cache
const reqId = Math.random();
this.inflightRequestId = reqId;
try {
const { data, dependencies } = await runQuery();
// Regardless if this request was cancelled or not, save the
// dependencies. The query can't change so all requests will
// return the same deps.
if (this._dependencies == null) {
this._dependencies = new Set(dependencies);
}
// Only fire events if this hasn't been cancelled and if we're
// still subscribed (`this.unsubscribeSyncEvent` will exist)
if (this.inflightRequestId === reqId && this._unsubscribeSyncEvent) {
const previousData = this.data;
this.data = data;
this.onData(this.data, previousData);
this.inflightRequestId = null;
}
} catch (e) {
console.log('Error fetching data', e);
this.onError(e);
}
🧰 Tools
🪛 Biome

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

Comment on lines 255 to 256
useEffect(() => {
if (accountId) {
dispatch(markAccountRead(accountId));
}
}, [accountId, dispatch]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using useLayoutEffect for synchronous updates

Based on previous implementations, such as the AllTransactions component where useLayoutEffect is used to dispatch actions synchronously before painting, consider using useLayoutEffect instead of useEffect for markAccountRead. This ensures that the account is marked as read before the component renders, preventing any potential UI inconsistencies.

Suggestion:

-useEffect(() => {
+useLayoutEffect(() => {
  if (accountId) {
    dispatch(markAccountRead(accountId));
  }
-}, [accountId, dispatch]);
+}, [accountId, dispatch]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (accountId) {
dispatch(markAccountRead(accountId));
}
}, [accountId, dispatch]);
useLayoutEffect(() => {
if (accountId) {
dispatch(markAccountRead(accountId));
}
}, [accountId, dispatch]);

Comment on lines 279 to 281
const { isSearching, search: onSearch } = useTransactionsSearch({
updateQuery: setTransactionsQuery,
resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
dateFormat,
});

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Verify dependencies in useTransactionsSearch

The useTransactionsSearch hook uses baseTransactionsQuery within resetQuery, but baseTransactionsQuery is defined outside of the hook without dependencies. To ensure resetQuery always has the latest baseTransactionsQuery, consider wrapping resetQuery with useCallback and adding baseTransactionsQuery to its dependency array.

Suggestion:

+const resetQuery = useCallback(() => {
+  setTransactionsQuery(baseTransactionsQuery());
+}, [baseTransactionsQuery]);

const { isSearching, search: onSearch } = useTransactionsSearch({
  updateQuery: setTransactionsQuery,
- resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
+ resetQuery,
  dateFormat,
});

Committable suggestion was skipped due to low confidence.

@youngcw youngcw added this to the v24.12.0 milestone Oct 22, 2024
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: 11

🧹 Outside diff range and nitpick comments (24)
packages/loot-core/src/client/data-hooks/filters.ts (2)

Line range hint 1-7: Consider removing @ts-strict-ignore

The presence of @ts-strict-ignore suggests potential type safety issues. Since TypeScript types are being used throughout the file (e.g., TransactionFilterEntity), consider addressing the type issues and enabling strict mode for better type safety.


Line range hint 8-20: Consider renaming toJS to better reflect its purpose

The function name toJS doesn't clearly convey its purpose of mapping database rows to TransactionFilterEntity objects. Consider a more descriptive name like mapRowsToFilters or transformToFilterEntities.

-function toJS(rows): TransactionFilterEntity[] {
+function mapRowsToFilters(rows): TransactionFilterEntity[] {
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3)

52-67: Optimize useEffect dependencies

The sync event handler properly manages table updates and cleanup. However, consider extracting the table check conditions to memoized values to optimize the effect's dependency array.

+ const shouldReloadTransactions = useMemo(
+   () => (tables) => (
+     tables.includes('transactions') ||
+     tables.includes('category_mapping') ||
+     tables.includes('payee_mapping')
+   ),
+   []
+ );

+ const shouldReloadPayees = useMemo(
+   () => (tables) => (
+     tables.includes('payees') ||
+     tables.includes('payee_mapping')
+   ),
+   []
+ );

  useEffect(() => {
    return listen('sync-event', ({ type, tables }) => {
      if (type === 'applied') {
-       if (
-         tables.includes('transactions') ||
-         tables.includes('category_mapping') ||
-         tables.includes('payee_mapping')
-       ) {
+       if (shouldReloadTransactions(tables)) {
          reloadTransactions?.();
        }

-       if (tables.includes('payees') || tables.includes('payee_mapping')) {
+       if (shouldReloadPayees(tables)) {
          dispatch(getPayees());
        }
      }
    });
-  }, [dispatch, reloadTransactions]);
+  }, [dispatch, reloadTransactions, shouldReloadTransactions, shouldReloadPayees]);

69-73: Add JSDoc comments for search configuration

Consider adding documentation to clarify the search configuration parameters and their impact.

+ /**
+  * Configures transaction search functionality
+  * @param {Object} config
+  * @param {Function} config.updateQuery - Updates the transaction query based on search
+  * @param {Function} config.resetQuery - Resets the query to its base state
+  * @param {string} config.dateFormat - Format used for date-based searches
+  */
  const { search: onSearch } = useTransactionsSearch({
    updateQuery: setTransactionsQuery,
    resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
    dateFormat,
  });

75-82: Enhance documentation of preview transaction handling

The comment about preview transactions could be more descriptive to help future maintainers.

- // details of how the native app used to handle preview transactions here can be found at commit 05e58279
+ /**
+  * Opens the transaction detail view.
+  * Preview transactions are skipped as they're temporary (see commit 05e58279).
+  * @param {Object} transaction - The transaction to open
+  */
packages/loot-core/src/shared/query.ts (2)

Line range hint 25-40: Consider enhancing type safety for tableOptions initialization.

While the constructor implementation is good, the tableOptions initialization could be more type-safe.

Consider this improvement:

- tableOptions: state.tableOptions || {},
+ tableOptions: state.tableOptions ?? {} as Record<string, unknown>,

Line range hint 145-164: Consider adding order value validation in getPrimaryOrderBy.

The function handles different order expression formats well, but it doesn't validate that the order value is either 'asc' or 'desc'.

Consider adding validation:

  const [field] = Object.keys(firstOrder);
- return { field, order: firstOrder[field] };
+ const order = firstOrder[field];
+ if (order !== 'asc' && order !== 'desc') {
+   throw new Error(`Invalid order value: ${order}. Expected 'asc' or 'desc'`);
+ }
+ return { field, order };
packages/loot-core/src/client/data-hooks/schedules.tsx (1)

151-173: Consider adding JSDoc documentation

The accountSchedulesQuery function would benefit from JSDoc documentation explaining:

  • The purpose of the function
  • The meaning of each accountId value ('budgeted', 'offbudget', 'uncategorized')
  • The structure of the returned query
+/**
+ * Creates a query to fetch schedules filtered by account
+ * @param accountId - The account to filter by:
+ *   - undefined: fetch all schedules
+ *   - 'budgeted': fetch schedules for budgeted accounts
+ *   - 'offbudget': fetch schedules for off-budget accounts
+ *   - 'uncategorized': fetch schedules with no next date
+ *   - string: fetch schedules for a specific account ID
+ * @returns A query object ordered by next_date in descending order
+ */
 export function accountSchedulesQuery(
   accountId?: AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized',
 ) {
packages/desktop-client/src/components/accounts/Balance.jsx (1)

157-157: Remove commented-out code.

These commented-out lines about transactionsQuery should be removed as they add noise to the codebase.

-  // const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' });
-          // transactionsQuery={transactionsQuery}

Also applies to: 220-221

packages/loot-core/src/client/data-hooks/transactions.ts (1)

60-63: Enhance error message for invalid query.

The error message could be more descriptive to help developers understand what went wrong.

   if (query.state.table !== 'transactions') {
-    setError(new Error('Query must be a transactions query.'));
+    setError(new Error('Invalid query: Expected table "transactions" but got "' + query.state.table + '".'));
     return;
   }
packages/loot-core/src/client/queries.ts (2)

31-34: LGTM! Consider adding type safety for the field parameter.

The type improvements for accountId parameter enhance compile-time safety. However, the field parameter could also benefit from type safety.

Consider adding a type for the field parameter:

-  field = 'account',
+  field: keyof Pick<SheetFields<'account'>, 'account'> = 'account',

Line range hint 83-124: Consider improving error handling and robustness.

The current implementation has several areas where it could be more defensive:

  1. Date parsing could throw on invalid input
  2. The $or array filtering could potentially be empty
  3. Amount parsing could be more robust

Consider applying these improvements:

 export function transactionsSearch(
   currentQuery: Query,
   search: string,
   dateFormat: SyncedPrefs['dateFormat'],
 ) {
   const amount = currencyToAmount(search);
 
   // Support various date formats
   let parsedDate;
+  try {
     if (getDayMonthRegex(dateFormat).test(search)) {
       parsedDate = parseDate(search, getDayMonthFormat(dateFormat), new Date());
     } else if (getShortYearRegex(dateFormat).test(search)) {
       parsedDate = parseDate(search, getShortYearFormat(dateFormat), new Date());
     } else {
       parsedDate = parseDate(search, dateFormat, new Date());
     }
+  } catch (e) {
+    parsedDate = null;
+  }
 
+  const conditions = [
+    isDateValid(parsedDate) && { date: dayFromDate(parsedDate) },
+    amount != null && {
+      amount: { $transform: '$abs', $eq: amountToInteger(amount) },
+    },
+    amount != null &&
+      Number.isInteger(amount) && {
+        amount: {
+          $transform: { $abs: { $idiv: ['$', 100] } },
+          $eq: amount,
+        },
+      },
+  ].filter(Boolean);
+
   return currentQuery.filter({
     $or: {
       'payee.name': { $like: `%${search}%` },
       notes: { $like: `%${search}%` },
       'category.name': { $like: `%${search}%` },
       'account.name': { $like: `%${search}%` },
-      $or: [
-        isDateValid(parsedDate) && { date: dayFromDate(parsedDate) },
-        amount != null && {
-          amount: { $transform: '$abs', $eq: amountToInteger(amount) },
-        },
-        amount != null &&
-          Number.isInteger(amount) && {
-            amount: {
-              $transform: { $abs: { $idiv: ['$', 100] } },
-              $eq: amount,
-            },
-          },
-      ].filter(Boolean),
+      ...(conditions.length > 0 ? { $or: conditions } : {}),
     },
   });
 }
packages/desktop-client/src/hooks/useSelected.tsx (1)

320-323: Enhance deprecation notice with migration details.

While the deprecation notice is good, consider enhancing it with:

  1. A brief migration guide or example
  2. Timeline for removal (if planned)
  3. Link to documentation (if available)

Example enhancement:

 /**
  * This can be helpful in class components if you cannot use the custom hook
  * @deprecated Use `SelectedProvider` and `useSelected` instead
+ * @example
+ * // Before
+ * <SelectedProviderWithItems items={items}>
+ *   {children}
+ * </SelectedProviderWithItems>
+ * 
+ * // After
+ * function MyComponent() {
+ *   const selected = useSelected('name', items, initialSelectedIds);
+ *   return (
+ *     <SelectedProvider instance={selected}>
+ *       {children}
+ *     </SelectedProvider>
+ *   );
+ * }
+ * @see [Migration Guide](link-to-docs)
+ * @removal Planned for v3.0.0
  */
packages/loot-core/src/shared/transactions.ts (5)

Line range hint 93-113: Consider adding runtime validation for transaction ordering.

While the code assumes transactions are sorted with parents before children (enforced by the DB layer), consider adding runtime validation to catch potential issues early, especially during testing or development.

 function findParentIndex(
   transactions: readonly TransactionEntity[],
   idx: number,
 ) {
+  // Validate that transactions are properly ordered
+  if (process.env.NODE_ENV !== 'production') {
+    const isOrdered = transactions.every((t, i) => 
+      !t.is_child || transactions.some((p, pi) => 
+        p.is_parent && p.id === t.parent_id && pi < i
+      )
+    );
+    if (!isOrdered) {
+      console.warn('Transactions are not properly ordered: parents must come before children');
+    }
+  }
   // This relies on transactions being sorted in a way where parents
   // are always before children, which is enforced in the db layer.

Line range hint 123-139: Consider optimizing array operations for better performance.

The current implementation uses reduce with array push operations, which can be less efficient for large transaction sets. Consider using flatMap for a more functional and potentially more efficient approach.

 export function ungroupTransactions(
   transactions: readonly TransactionEntity[],
 ) {
-  return transactions.reduce<TransactionEntity[]>((list, parent) => {
+  return transactions.flatMap(parent => {
     const { subtransactions, ...trans } = parent;
     const _subtransactions = subtransactions || [];
-
-    list.push(trans);
-
-    for (let i = 0; i < _subtransactions.length; i++) {
-      list.push(_subtransactions[i]);
-    }
-    return list;
-  }, []);
+    return [trans, ..._subtransactions];
+  });
 }

Line range hint 163-227: Enhance error handling and consider splitting responsibilities.

The replaceTransactions function handles multiple concerns and could benefit from:

  1. More specific error types instead of generic Error
  2. Separation into smaller, focused functions
  3. Better error recovery for edge cases

Consider splitting the function into smaller utilities:

interface TransactionError extends Error {
  type: 'NOT_FOUND' | 'PARENT_NOT_FOUND';
  transactionId: string;
}

function findTransactionWithParent(transactions: readonly TransactionEntity[], id: string): {
  transaction: TransactionEntity;
  parentIndex: number | null;
} {
  // Extract transaction finding logic
}

function generateTransactionDiff(
  original: readonly TransactionEntity[],
  updated: TransactionEntity[]
): ReturnType<typeof diffItems<TransactionEntity>> {
  // Extract diff generation logic
}

Line range hint 355-393: Improve documentation and clarify complex logic.

The makeAsNonChildTransactions function would benefit from:

  1. JSDoc documentation explaining the purpose and behavior
  2. Clear explanation of the logic for handling remaining child transactions
  3. Type annotations for intermediate calculations

Add comprehensive documentation:

/**
 * Converts selected child transactions to standalone transactions while maintaining
 * the integrity of the remaining parent-child relationships.
 *
 * @param childTransactionsToUpdate - Child transactions to convert to standalone
 * @param transactions - Array of transactions starting with the parent followed by children
 * @returns Object containing updated transactions and deleted transactions
 *
 * @example
 * const result = makeAsNonChildTransactions([childTrans1], [parentTrans, childTrans1, childTrans2]);
 * // result.updated = [childTrans1AsNonChild]
 * // result.deleted = [parentTrans] // if only one child remains
 */

Line range hint 1-393: Overall assessment: Approved with suggestions for improvement.

The changes to make arrays readonly improve type safety and prevent accidental mutations. The code handles complex transaction relationships well, but consider the following improvements:

  1. Add comprehensive input validation
  2. Enhance error handling with specific error types
  3. Improve documentation, especially for complex logic
  4. Add runtime checks for development environments
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)

Line range hint 261-275: Consider simplifying the completed schedules logic

The filtering logic could be more concise while maintaining the same functionality.

  const items: readonly SchedulesTableItem[] = useMemo(() => {
    const unCompletedSchedules = filteredSchedules.filter(s => !s.completed);
-   if (!allowCompleted) {
-     return unCompletedSchedules;
-   }
-   if (showCompleted) {
-     return filteredSchedules;
-   }
-   const hasCompletedSchedule = filteredSchedules.find(s => s.completed);
-   if (!hasCompletedSchedule) return unCompletedSchedules;
-   return [...unCompletedSchedules, { id: 'show-completed' }];
+   if (!allowCompleted || showCompleted) {
+     return allowCompleted ? filteredSchedules : unCompletedSchedules;
+   }
+   return filteredSchedules.some(s => s.completed)
+     ? [...unCompletedSchedules, { id: 'show-completed' }]
+     : unCompletedSchedules;
  }, [filteredSchedules, showCompleted, allowCompleted]);

Line range hint 406-412: Remove unnecessary type casting of items

The items array is being unnecessarily cast to ScheduleEntity[]. The Table component should accept the union type SchedulesTableItem.

- items={items as ScheduleEntity[]}
+ items={items}
packages/loot-core/src/client/query-helpers.ts (2)

4-4: LGTM! Well-structured type definitions with room for minor improvement.

The introduction of TypeScript types and generics significantly improves type safety and API clarity. The use of structured parameters and ReadonlyArray is particularly good.

Consider adding JSDoc comments for the exported types to improve documentation:

/** Represents the response data as a read-only array */
type Data<TResponse> = ReadonlyArray<TResponse>;

/** Callback for handling data updates with previous state */
type Listener<TResponse = unknown> = (
  data: Data<TResponse>,
  previousData: Data<TResponse>,
) => void;

Also applies to: 10-57


Line range hint 383-417: Address potential race condition in _fetchNext method.

While the method attempts to handle concurrent requests, there's still a potential race condition:

  1. The check for inflightRequestId and data comparison happens after the new data is fetched
  2. This could lead to data inconsistency if multiple fetchNext calls occur simultaneously

Consider this more robust implementation:

 private _fetchNext = async () => {
+  const currentRequestId = Math.random();
+  this.inflightRequestId = currentRequestId;
+
   while (this.inflightRequestId) {
     await this._fetchDataPromise;
   }

+  // Another request has started
+  if (this.inflightRequestId !== currentRequestId) {
+    return this._fetchNext();
+  }
+
   const previousData = this.data;

   if (!this._hasReachedEnd) {
     const { data } = await runQuery(
       this.query.limit(this._pageCount).offset(previousData.length),
     );

-    // If either there is an existing request in flight or the data
-    // has already changed underneath it, we can't reliably concat
-    // the data since it's different now. Need to re-run the whole
-    // process again
-    if (this.inflightRequestId || previousData !== this.data) {
+    // If another request has started or data has changed, retry
+    if (this.inflightRequestId !== currentRequestId || previousData !== this.data) {
       return this._fetchNext();
     } else {
       if (data.length === 0) {
         this._hasReachedEnd = true;
       } else {
         this._hasReachedEnd = data.length < this._pageCount;
         const prevData = this.data;
         this.updateData(currentData => currentData.concat(data));
         this.onPageData(data);
         this.onData(this.data, prevData);
       }
     }
   }
+  
+  if (this.inflightRequestId === currentRequestId) {
+    this.inflightRequestId = null;
+  }
 };
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)

769-776: LGTM! Good handling of loading state.

The changes properly handle the loading state of schedules, preventing potential undefined access. The early return pattern is clean and effective.

Consider destructuring with a default empty array to avoid potential undefined access:

-  const { isLoading, schedules = [] } = useCachedSchedules();
+  const { isLoading, schedules } = useCachedSchedules() ?? { isLoading: true, schedules: [] };

Line range hint 1765-1775: LGTM! Good propagation of loading state.

The loading state is correctly propagated from TransactionTableInner to the Table component, enabling proper loading state handling.

Consider adding JSDoc or PropTypes documentation for the new isLoading prop:

/**
 * @typedef {Object} TransactionTableInnerProps
 * @property {boolean} isLoading - Indicates if the transaction data is currently being loaded
 * ...other props
 */

Also applies to: 1994-1994

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d0640a7 and ac752c6.

⛔ Files ignored due to path filters (2)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (43)
  • .eslintrc.js (1 hunks)
  • packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
  • packages/desktop-client/e2e/accounts.test.js (2 hunks)
  • packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
  • packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
  • packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
  • packages/loot-core/src/client/queries.ts (2 hunks)
  • packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
  • packages/loot-core/src/client/query-hooks.ts (1 hunks)
  • packages/loot-core/src/shared/query.ts (9 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useSchedules.ts
🚧 Files skipped from review as they are similar to previous changes (23)
  • packages/desktop-client/e2e/accounts.mobile.test.js
  • packages/desktop-client/e2e/accounts.test.js
  • packages/desktop-client/e2e/page-models/account-page.js
  • packages/desktop-client/e2e/page-models/mobile-account-page.js
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js
  • packages/desktop-client/src/components/ManageRules.tsx
  • packages/desktop-client/src/components/accounts/Header.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/schedules/index.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useNotes.ts
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/loot-core/src/client/data-hooks/dashboard.ts
  • packages/loot-core/src/client/data-hooks/widget.ts
  • packages/loot-core/src/client/query-helpers.test.ts
🧰 Additional context used
📓 Learnings (5)
.eslintrc.js (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: .eslintrc.js:164-169
Timestamp: 2024-10-18T15:33:24.453Z
Learning: When updating the `additionalHooks` pattern for `react-hooks/exhaustive-deps`, only `useQuery` should be added, not generalized patterns.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277
Timestamp: 2024-10-22T05:34:56.976Z
Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
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.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
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.
packages/loot-core/src/client/data-hooks/reports.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/loot-core/src/client/query-helpers.ts

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🔇 Additional comments (45)
packages/loot-core/src/client/query-hooks.ts (6)

7-11: LGTM! Well-structured type definition.

The UseQueryResult type is well-defined with proper immutability constraints using ReadonlyArray and includes all necessary fields for data, loading state, and error handling.


14-16: LGTM! Improved function signature with better type safety.

The updated signature allows for conditional querying with null queries and uses a more descriptive parameter name dependencies. The return type properly leverages the new UseQueryResult type.


17-21: LGTM! Appropriate use of memoization with justified ESLint disable.

The memoization strategy is correct, and the ESLint rule disable is well-documented and justified since we only care about the resulting query, not the function that creates it.


23-25: LGTM! Well-structured state management.

The state initialization is logically sound with proper typing and the loading state correctly reflects the query's presence.


35-38: Previous review comment is still applicable.

The issue with using a local isUnmounted variable inside useEffect that could lead to stale closures remains unaddressed.


52-57: LGTM! Return value matches type definition.

The return value properly includes all fields defined in UseQueryResult type.

packages/loot-core/src/client/data-hooks/reports.ts (3)

8-8: LGTM: Import change aligns with hook migration.

The import change from useLiveQuery to useQuery is correct and aligns with the learnings that indicate useLiveQuery was a wrapper around useQuery.


39-42: LGTM: Proper implementation of useQuery with type safety.

The migration from useLiveQuery to useQuery is implemented correctly:

  • Maintains type safety with CustomReportData
  • Properly destructures both data and isLoading states
  • Query function and dependency array are correctly specified

57-58: Verify the empty array fallback behavior.

The implementation handles the loading and null states correctly, but let's verify the behavior when queryData is null or undefined to ensure it matches the previous implementation.

Also applies to: 60-60

✅ Verification successful

Let me try a different search strategy to verify the behavior.


Let me check the implementation of the useReports hook and its usage more thoroughly.


Based on the implementation shown in the context, I can now generate a final response.

The empty array fallback behavior is correctly implemented

The implementation in useReports hook is handling the null/undefined case appropriately:

  • It uses the spread operator on queryData only when it exists: [...queryData]
  • The ternary expression queryData ? [...queryData] : [] ensures an empty array is returned when queryData is null/undefined
  • The sort function expects and operates on an array of CustomReportEntity, so the empty array fallback maintains type safety
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for tests or usages that verify the behavior when queryData is null/undefined
rg -l "useReports.*null|useReports.*undefined" --type ts --type tsx --type test

Length of output: 109


Script:

#!/bin/bash
# Search for the implementation of useReports and its tests
rg -A 5 "useReports" --type ts

# Also search for test files related to reports
fd "reports.*test" -e ts -e js

# Look for the sort function implementation
ast-grep --pattern 'function sort($_) { $$$ }'

Length of output: 5158


Script:

#!/bin/bash
# Get the full implementation of useReports hook and surrounding context
rg -B 10 -A 10 "export function useReports" --type ts

# Look for any tests that might verify report data handling
rg -A 5 "reports.*test" --type ts --type js

# Check how queryData is used in the reports file
ast-grep --pattern 'queryData ? $$ : $$'

Length of output: 1799

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3)

1-8: LGTM! Clean organization of imports

The imports are well-structured and the new transaction hooks align with the PR's objective of modernizing the codebase.


114-116: LGTM! Clean prop passing

The component properly forwards the loading state and callbacks to the TransactionListWithBalances component.


40-47: Verify transaction loading behavior

The implementation of useTransactions looks clean. However, we should verify that the loading states are properly handled throughout the component lifecycle.

✅ Verification successful

Let me try a different approach to verify the transaction loading behavior.

This will help us understand:

  1. How the hook is used across components
  2. The implementation details of the hook itself
  3. The loading state handling patterns

Let me try one more search to specifically look at the implementation of the useTransactions hook.

This will help us verify the loading state implementation in the hook itself.


Based on the gathered information, I can now provide a final response about the transaction loading behavior verification.

Loading state implementation is properly handled

The implementation in CategoryTransactions.jsx correctly uses the loading states from useTransactions. The hook itself has proper loading state management:

  • Uses useState to track loading state
  • Properly initializes isLoading state
  • Exposes the loading state through the hook's return value
  • The loading state is consistently used across multiple components (Account, CategoryTransactions, AccountTransactions)

The implementation follows React hooks best practices and maintains consistency across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for proper loading state handling in other components
rg -l "isLoading.*useTransactions" | while read -r file; do
  echo "=== $file ==="
  rg -A 5 "isLoading.*useTransactions" "$file"
done

Length of output: 51


Script:

#!/bin/bash
# First, let's find all usages of useTransactions hook
rg "useTransactions" -A 5 -B 2

# Also check for the hook's implementation
ast-grep --pattern 'const useTransactions = $_'

Length of output: 12517


Script:

#!/bin/bash
# Get the implementation details of useTransactions hook
rg -A 20 "export function useTransactions" packages/loot-core/src/client/data-hooks/transactions.ts

Length of output: 1429

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

1-13: Well-structured type definitions with proper type safety!

The introduction of ObjectExpression type and the enhanced QueryState type provides good type safety and flexibility for complex query expressions.


67-74: Well-typed select method signature!

The comprehensive type union effectively covers all possible selection scenarios while maintaining type safety.


136-138: Verify reset method behavior.

The reset method creates a new query instance, but we should verify that this doesn't cause any issues with existing query chains or references.

✅ Verification successful

Let me try a different approach to verify the reset method behavior by checking its usage patterns and potential impacts.


Let me search for actual usages of the Query class's reset method to understand its impact.


Based on the gathered information, I can now provide a final response about the reset method's behavior.

The reset method implementation is safe and follows the Query class's immutable pattern

The reset method is well-implemented because:

  1. It follows the same immutable pattern as other Query methods by creating a new Query instance
  2. It uses the q() helper function which is consistent across the codebase (found in both shared/query.ts and api/app/query.js)
  3. The Query class methods all return new instances, preventing mutation of existing queries
  4. No direct chaining of reset() was found in the codebase, indicating it's typically used as a terminal operation

The implementation maintains the class's immutable design pattern and won't cause issues with existing query chains since each Query operation returns a new instance rather than modifying the existing one.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for chained query operations that might be affected by reset
rg -l "\.reset\(\)" | xargs rg -A 3 "\.reset\(\)\."

Length of output: 53


Script:

#!/bin/bash
# Search for all usages of reset() method to understand usage patterns
rg -A 3 "\.reset\(" 

# Also search for the query class definition and its usage to understand the context
ast-grep --pattern 'class Query {
  $$$
  reset() {
    $$$
  }
  $$$
}'

Length of output: 9528


Script:

#!/bin/bash
# Search for usages of reset() in the context of Query
rg -B 3 -A 3 "\.reset\(\)" packages/loot-core/src/

# Also search for the q function that's used in reset()
ast-grep --pattern 'function q($_) {
  $$$
}'

Length of output: 3065

packages/loot-core/src/client/data-hooks/schedules.tsx (3)

7-20: LGTM: Import and type changes enhance type safety

The additions of proper TypeScript types and the useRef hook improve type safety and component lifecycle management.


26-51: Consider wrapping the onError callback

The onError callback is passed directly to liveQuery, which could lead to state updates after unmount. While the parent useSchedules hook has unmount protection, it would be safer to wrap this callback too.


128-136: LGTM: Well-structured context implementation

The context implementation follows React best practices with proper TypeScript types and default values.

packages/desktop-client/src/components/accounts/Balance.jsx (6)

74-76: LGTM: Good loading state handling.

The addition of loading state check prevents rendering with incomplete data, improving reliability.


117-124: LGTM: Improved prop naming and null handling.

Good improvements:

  • More consistent prop naming with filteredBalance
  • Proper null handling with || 0

128-137: LGTM: Improved query caching.

Good improvement in query caching by including accountId in the query names, ensuring proper cache isolation between different accounts.


158-165: LGTM: Good performance optimization.

The memoization of balance binding prevents unnecessary recalculations and re-renders.


176-195: LGTM: Enhanced button implementation.

Good improvements:

  • Added loading state handling
  • Clean emotion CSS implementation with proper hover states
  • Improved SVG icon styling and transitions

Line range hint 90-94: Verify the schedule balance calculation logic.

The condition for adding/subtracting scheduled amounts has been updated to use accountId. Please ensure this change maintains the correct balance calculation for transfers between accounts.

✅ Verification successful

Let me try a different search strategy to verify the schedule balance calculation logic.

These commands will help us understand:

  1. How scheduleBalance is used throughout the codebase
  2. The context of getScheduledAmount usage
  3. The full component implementation to verify the logic

Based on the search results, I can now provide a conclusive response about the schedule balance calculation logic.

The schedule balance calculation logic is correctly implemented

The code change from account to accountId is consistent with the codebase's implementation. Here's why:

  1. The getScheduledAmount function is properly imported from 'loot-core/src/shared/schedules' and is used consistently across the codebase for handling scheduled amounts.

  2. The balance calculation logic follows the correct pattern for transfers:

    • When the schedule's account (s._account) doesn't match the current accountId, it adds the amount (representing incoming transfer)
    • When they match, it subtracts the amount (representing outgoing transfer)
  3. The implementation is consistent with how scheduled transactions are handled in other parts of the application, as seen in the TransactionsTable and SchedulesTable components.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other instances of schedule balance calculations
rg -A 5 '_account.*scheduleBalance'

Length of output: 37


Script:

#!/bin/bash
# Search for schedule balance related code with broader context
rg -A 10 'scheduleBalance' 

# Also search for related terms to understand the context better
rg -A 10 'getScheduledAmount'

# Look for the component definition to understand the full context
ast-grep --pattern 'function Balance($_) {
  $$$
}'

Length of output: 20237

packages/loot-core/src/client/data-hooks/transactions.ts (1)

1-16: LGTM! Well-organized imports and type declarations.

The imports are logically grouped and properly use the type keyword for type imports.

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

70-72: LGTM! Clean and consistent implementation.

The function maintains type safety and properly integrates with the accountFilter function. The grouped splits option ensures consistent transaction handling.

Also applies to: 75-79


Line range hint 31-124: Verify all imports and usages have been updated.

Since these functions have been renamed, we should verify that all imports and usages have been updated accordingly.

✅ Verification successful

Let me analyze the results and gather more specific information about the function usage.

This will help us verify if the functions are being used with the correct name and parameters in the codebase. The previous results showed many files containing these terms, but we need to see the actual function calls to ensure they're being used correctly.


Based on the results, I can now generate the final response as we have clear evidence about the usage of these functions.

All function renames have been properly implemented and referenced

The verification results show that:

  1. No old function names (getAccountFilter, makeTransactionsQuery, makeTransactionSearchQuery) are found in the codebase
  2. The new function names are properly used throughout the codebase:
    • accountFilter is used in queries.ts and schedules.tsx
    • transactions is used in Account.tsx and AccountTransactions.tsx
    • transactionsSearch is used in transactions.ts data hook

All usages follow the correct function signatures and parameter patterns, indicating a complete and correct rename implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining references to the old function names

echo "Checking for old function names..."
rg -l "getAccountFilter|makeTransactionsQuery|makeTransactionSearchQuery"

echo "Verifying imports of new function names..."
rg -l "accountFilter|transactions|transactionsSearch"

Length of output: 9556


Script:

#!/bin/bash
# Let's check for actual usage of these functions in code files
echo "Checking accountFilter usage..."
rg "accountFilter\(" -A 2

echo -e "\nChecking transactions function usage..."
rg "transactions\(" -A 2

echo -e "\nChecking transactionsSearch usage..."
rg "transactionsSearch\(" -A 2

Length of output: 3911

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6)

21-28: LGTM: Clean implementation of new hooks and query structure

The migration to use the new transaction hooks and the refactored schedules query implementation looks good. The useMemo for schedulesQuery is properly implemented with the correct dependency array.

Also applies to: 59-62


Line range hint 106-155: LGTM: Well-structured callback implementations

The callbacks are properly memoized with correct dependency arrays, following React best practices. The implementation is clean and efficient.


217-222: LGTM: Improved type safety with extended accountId types

The type definitions have been properly extended to support special account types ('budgeted', 'offbudget', 'uncategorized'), improving type safety and aligning with the query system.


224-240: LGTM: Clean implementation of transaction query management

The transaction query management is well-implemented with proper memoization and state management. The usage of the new useTransactions hook aligns with the PR objectives.


255-259: Consider using useLayoutEffect for synchronous updates

Based on previous implementations in the codebase, consider using useLayoutEffect for marking the account as read to ensure synchronous updates before rendering.


315-318: LGTM: Clean implementation of transaction display logic

The transaction display logic is well-implemented with proper handling of preview and regular transactions based on the search state.

packages/desktop-client/src/hooks/useSelected.tsx (1)

51-51: LGTM! Type safety improvements.

The changes enhance type safety by:

  1. Making the Actions type internal to prevent external dependencies
  2. Making array parameters readonly to prevent accidental mutations

Also applies to: 55-56

packages/loot-core/src/shared/transactions.ts (1)

Line range hint 229-334: Add input validation and state consistency checks.

The transaction modification functions could benefit from stronger validation to prevent invalid states:

  1. Validate transaction amounts (non-negative, valid numbers)
  2. Ensure parent-child relationship consistency
  3. Verify transaction dates are valid

Let's verify the current validation coverage:

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)

39-40: LGTM: Type safety improvements

Good improvements to the type definitions:

  • Making schedules readonly prevents accidental mutations
  • Adding isLoading prop enables proper loading state handling

278-343: LGTM: Proper memoization of render function

Good use of useCallback with appropriate dependencies to prevent unnecessary re-renders.

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/loot-core/src/client/query-helpers.ts (1)

163-176: ⚠️ Potential issue

Prevent potential memory leak in subscribe method.

The subscribe method doesn't check if there's an existing listener before adding a new one. This could lead to multiple listeners being registered if subscribe() is called multiple times before unsubscribe().

Consider this safer implementation:

 protected subscribe = () => {
   if (this._unsubscribeSyncEvent == null) {
+    // Ensure any existing listener is cleaned up
+    if (this._unsubscribeSyncEvent) {
+      this._unsubscribeSyncEvent();
+    }
     this._unsubscribeSyncEvent = listen('sync-event', ({ type, tables }) => {
       if (this._supportedSyncTypes.has(type)) {
         this.onUpdate(tables);
       }
     });
   }
 };

Likely invalid or redundant comment.

.eslintrc.js (2)

164-169: LGTM! The react-hooks/exhaustive-deps rule configuration is correct.

The addition of useQuery to additionalHooks aligns with the best practices for hook dependency checking.


164-169: Verify if the Account component should be exempted from the exhaustive-deps rule.

I notice that ./packages/desktop-client/src/components/accounts/Account.jsx is listed in the override section where this rule is disabled. Since this PR is migrating the Account component to use hooks, consider if it should still be exempted from the exhaustive-deps rule.

Let's check if the Account component is still using class components or has been fully migrated:

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

8-8: LGTM: Clean integration of the useCategories hook.

The hook is properly imported and initialized, with destructuring used to access the categories list.

Also applies to: 158-159


789-789: LGTM: Proper prop passing to Transaction component.

The categories are correctly passed down to the Transaction component.


313-313: Verify dependency array completeness.

The getImportPreview useCallback dependency array includes all necessary dependencies.

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

Line range hint 842-856: LGTM! Clean implementation of loading state

The implementation correctly uses ButtonWithLoading component and properly handles the loading state. The default value of false for isLoading ensures backward compatibility.


819-819: ⚠️ Potential issue

Make isLoading optional in SelectedItemsButtonProps

The isLoading property should be optional since it has a default value in the component implementation. This maintains backward compatibility with existing usages of SelectedItemsButton.

Apply this diff to make the property optional:

-  isLoading: boolean;
+  isLoading?: boolean;

Likely invalid or redundant comment.

Comment on lines 28 to 36
return useMemo(
() =>
toJS(data ? [...data] : []).sort((a, b) =>
a.name
.trim()
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }),
),
[data],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize array operations by removing unnecessary spread

The spread operator in [...data] creates an unnecessary copy of the array since sort is already being called on the result of toJS which creates a new array.

-      toJS(data ? [...data] : []).sort((a, b) =>
+      toJS(data || []).sort((a, b) =>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return useMemo(
() =>
toJS(data ? [...data] : []).sort((a, b) =>
a.name
.trim()
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }),
),
[data],
);
return useMemo(
() =>
toJS(data || []).sort((a, b) =>
a.name
.trim()
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }),
),
[data],
);

Comment on lines 23 to 25
const { data } = useQuery<TransactionFilterEntity>(
() => q('transaction_filters').select('*'),
[],
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the results, I can now generate the final response since we have found evidence of mutations to the transaction_filters table through filter-create, filter-update, and filter-delete methods in the server code.

Add query dependencies to reflect filter mutations

The transaction filters query should be refreshed when filters are created, updated, or deleted. The current empty dependency array [] prevents this. Add the necessary query dependencies:

const { data } = useQuery<TransactionFilterEntity>(
  () => q('transaction_filters').select('*'),
  ['filters'], // Add dependency to refresh on filter mutations
  • The server implements mutations via filter-create, filter-update, and filter-delete methods in packages/loot-core/src/server/filters/app.ts
  • These mutations should trigger a query refresh to keep the UI in sync with the latest filter state
🔗 Analysis chain

Verify if query dependencies are needed

The empty dependency array [] suggests this query never needs to be re-run. Please verify if there are any conditions under which the transaction filters query should be refreshed.


Let me gather more information about the transaction filters usage and mutations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for mutations or updates to transaction_filters
rg -A 5 "transaction_filters.*update|transaction_filters.*insert|transaction_filters.*delete"

Length of output: 634


Script:

#!/bin/bash
# Search for all references to transaction_filters table operations
ast-grep --pattern 'q("transaction_filters")' 
ast-grep --pattern "q('transaction_filters')"

# Look for filter creation/update/delete operations
rg -A 5 "createFilter|updateFilter|deleteFilter"

Length of output: 4034

isLoading: queryData === null,
data: sort(toJS(queryData || [])),
isLoading,
data: sort(toJS(queryData ? [...queryData] : [])),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider removing unnecessary spread operation.

The spread operation [...queryData] creates a new array unnecessarily since:

  1. toJS already creates a new array through .map()
  2. sort also returns a new array
-      data: sort(toJS(queryData ? [...queryData] : [])),
+      data: sort(toJS(queryData || [])),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data: sort(toJS(queryData ? [...queryData] : [])),
data: sort(toJS(queryData || [])),

Comment on lines 28 to 39
const baseTransactionsQuery = useCallback(
() =>
q('transactions')
.options({ splits: 'inline' })
.filter(getCategoryMonthFilter(category, month)),
.filter(getCategoryMonthFilter(category, month))
.select('*'),
[category, month],
);

const paged = useRef(null);

const updateQuery = useCallback(query => {
paged.current?.unsubscribe();
setIsLoading(true);
paged.current = pagedQuery(
query.options({ splits: 'inline' }).select('*'),
data => {
setTransactions(data);
setIsLoading(false);
},
{ pageCount: 50 },
);
}, []);
const [transactionsQuery, setTransactionsQuery] = useState(
baseTransactionsQuery(),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing the filter object

While the useCallback is correctly implemented, we could further optimize by memoizing the filter object to prevent unnecessary query reconstructions.

+ const categoryMonthFilter = useMemo(
+   () => getCategoryMonthFilter(category, month),
+   [category, month]
+ );

  const baseTransactionsQuery = useCallback(
    () =>
      q('transactions')
        .options({ splits: 'inline' })
-       .filter(getCategoryMonthFilter(category, month))
+       .filter(categoryMonthFilter)
        .select('*'),
-   [category, month],
+   [categoryMonthFilter],
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const baseTransactionsQuery = useCallback(
() =>
q('transactions')
.options({ splits: 'inline' })
.filter(getCategoryMonthFilter(category, month)),
.filter(getCategoryMonthFilter(category, month))
.select('*'),
[category, month],
);
const paged = useRef(null);
const updateQuery = useCallback(query => {
paged.current?.unsubscribe();
setIsLoading(true);
paged.current = pagedQuery(
query.options({ splits: 'inline' }).select('*'),
data => {
setTransactions(data);
setIsLoading(false);
},
{ pageCount: 50 },
);
}, []);
const [transactionsQuery, setTransactionsQuery] = useState(
baseTransactionsQuery(),
);
const categoryMonthFilter = useMemo(
() => getCategoryMonthFilter(category, month),
[category, month]
);
const baseTransactionsQuery = useCallback(
() =>
q('transactions')
.options({ splits: 'inline' })
.filter(categoryMonthFilter)
.select('*'),
[categoryMonthFilter],
);
const [transactionsQuery, setTransactionsQuery] = useState(
baseTransactionsQuery(),
);

Comment on lines 69 to 74
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | undefined>(undefined);
const [data, setData] = useState<ScheduleData>({
schedules: [],
statuses: new Map(),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider optimizing state management

The initial state setup and error/loading state management could be optimized:

- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState<Error | undefined>(undefined);
- const [data, setData] = useState<ScheduleData>({
-   schedules: [],
-   statuses: new Map(),
- });
+ const [state, setState] = useState<{
+   isLoading: boolean;
+   error?: Error;
+   data: ScheduleData;
+ }>({
+   isLoading: false,
+   data: {
+     schedules: [],
+     statuses: new Map(),
+   },
+ });

  useEffect(() => {
-   setError(undefined);
-   setIsLoading(!!query);
+   setState(prev => ({
+     ...prev,
+     error: undefined,
+     isLoading: !!query,
+   }));

This reduces the number of state updates and prevents potential race conditions.

Also applies to: 83-84

Comment on lines 146 to 179
Promise.all(
scheduleTransactions.map(transaction =>
send('rules-run', { transaction }),
),
)
.then(newTrans => {
if (!isUnmounted) {
const withDefaults = newTrans.map(t => ({
...t,
category: statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map(
(st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
}),
),
}));

setIsLoading(false);
setPreviewTransactions(ungroupTransactions(withDefaults));
}
})
.catch(error => {
if (!isUnmounted) {
setError(error);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding cleanup for pending promises.

The Promise.all chain could continue executing after component unmount. While the state updates are protected, the rules-run operations will still complete unnecessarily.

Consider using an AbortController to cancel pending operations:

   useEffect(() => {
     let isUnmounted = false;
+    const abortController = new AbortController();
 
     setError(undefined);
 
     if (scheduleTransactions.length === 0) {
       setPreviewTransactions([]);
       return;
     }
 
     setIsLoading(true);
 
     Promise.all(
       scheduleTransactions.map(transaction =>
-        send('rules-run', { transaction }),
+        send('rules-run', { transaction, signal: abortController.signal }),
       ),
     )
       .then(newTrans => {
         if (!isUnmounted) {
           // ... existing code ...
         }
       })
       .catch(error => {
-        if (!isUnmounted) {
+        if (!isUnmounted && error.name !== 'AbortError') {
           setError(error);
         }
       });
 
     return () => {
       isUnmounted = true;
+      abortController.abort();
     };
   }, [scheduleTransactions, schedules, statuses]);

Committable suggestion was skipped due to low confidence.

Comment on lines 208 to 227
const updateSearchQuery = useMemo(
() =>
debounce((searchText: string) => {
if (searchText === '') {
resetQuery();
setIsSearching(false);
} else if (searchText) {
updateQuery(previousQuery =>
queries.transactionsSearch(previousQuery, searchText, dateFormat),
);
setIsSearching(true);
}
}, delayMs),
[dateFormat, delayMs, resetQuery, updateQuery],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance search input handling and state management.

  1. The search function doesn't handle undefined/null input.
  2. The isSearching state remains true even after the search completes.
   const updateSearchQuery = useMemo(
     () =>
       debounce((searchText: string) => {
+        const trimmedText = searchText?.trim() ?? '';
-        if (searchText === '') {
+        if (trimmedText === '') {
           resetQuery();
           setIsSearching(false);
-        } else if (searchText) {
+        } else {
           updateQuery(previousQuery =>
-            queries.transactionsSearch(previousQuery, searchText, dateFormat),
+            queries.transactionsSearch(previousQuery, trimmedText, dateFormat),
           );
           setIsSearching(true);
+          // Reset isSearching after the debounce delay
+          setTimeout(() => setIsSearching(false), delayMs);
         }
       }, delayMs),
     [dateFormat, delayMs, resetQuery, updateQuery],
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const updateSearchQuery = useMemo(
() =>
debounce((searchText: string) => {
if (searchText === '') {
resetQuery();
setIsSearching(false);
} else if (searchText) {
updateQuery(previousQuery =>
queries.transactionsSearch(previousQuery, searchText, dateFormat),
);
setIsSearching(true);
}
}, delayMs),
[dateFormat, delayMs, resetQuery, updateQuery],
);
const updateSearchQuery = useMemo(
() =>
debounce((searchText: string) => {
const trimmedText = searchText?.trim() ?? '';
if (trimmedText === '') {
resetQuery();
setIsSearching(false);
} else {
updateQuery(previousQuery =>
queries.transactionsSearch(previousQuery, trimmedText, dateFormat),
);
setIsSearching(true);
// Reset isSearching after the debounce delay
setTimeout(() => setIsSearching(false), delayMs);
}
}, delayMs),
[dateFormat, delayMs, resetQuery, updateQuery],
);

Comment on lines 86 to 96
return {
transactions,
isLoading,
error,
reload: pagedQueryRef.current?.run,
loadMore: pagedQueryRef.current?.fetchNext,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider providing no-op functions instead of undefined.

When pagedQueryRef.current is null, reload and loadMore will be undefined. This could lead to runtime errors if the consumer doesn't check for undefined before calling these functions.

   return {
     transactions,
     isLoading,
     error,
-    reload: pagedQueryRef.current?.run,
-    loadMore: pagedQueryRef.current?.fetchNext,
+    reload: () => pagedQueryRef.current?.run(),
+    loadMore: () => pagedQueryRef.current?.fetchNext(),
   };

Committable suggestion was skipped due to low confidence.

Comment on lines +312 to +313
items: readonly T[];
initialSelectedIds?: readonly string[];
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the gathered information, I can now provide the final response:

Component is deprecated and unused - consider removal

The SelectedProviderWithItems component is:

  • Marked as deprecated with a clear migration path: "@deprecated Use SelectedProvider and useSelected instead"
  • Not imported or used anywhere in the codebase
  • Only exists in packages/desktop-client/src/hooks/useSelected.tsx

Instead of adding type improvements to this deprecated component, consider removing it since it's not being used and has a documented replacement.

🔗 Analysis chain

Verify usage of deprecated component.

While the readonly type improvements are good, let's verify the usage of this deprecated component to assess the impact of eventual removal.


Let me try a different approach to gather the necessary information since there seems to be an issue with the file type recognition.


Let me search for actual usage of this component in the codebase to verify if it's being used anywhere.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find usages of SelectedProviderWithItems to assess migration impact

# Search for component usage
echo "Searching for SelectedProviderWithItems usage..."
rg "SelectedProviderWithItems" --type ts --type tsx

# Search for potential class components that might need migration
echo "Searching for class components that might need migration..."
ast-grep --pattern 'class $_ extends React.Component {
  $$$
}'

Length of output: 381


Script:

#!/bin/bash
# Search for SelectedProviderWithItems usage with ripgrep, explicitly including both .ts and .tsx files
echo "Searching for SelectedProviderWithItems usage..."
rg "SelectedProviderWithItems" -g "*.{ts,tsx}"

# Also search for the export of this component to understand its location and documentation
echo "Searching for SelectedProviderWithItems definition..."
rg "export.*SelectedProviderWithItems" -g "*.{ts,tsx}"

# Look for any deprecation comments or warnings
echo "Searching for deprecation markers..."
rg "@deprecated.*SelectedProviderWithItems" -g "*.{ts,tsx}"
rg "// deprecated.*SelectedProviderWithItems" -g "*.{ts,tsx}"

Length of output: 952


Script:

#!/bin/bash
# Search for JSX usage of SelectedProviderWithItems
echo "Searching for JSX usage of SelectedProviderWithItems..."
rg "<SelectedProviderWithItems" -g "*.{ts,tsx}" -A 2

# Search for any imports of SelectedProviderWithItems
echo "Searching for imports of SelectedProviderWithItems..."
rg "import.*SelectedProviderWithItems.*from" -g "*.{ts,tsx}"

# Look for any comments containing 'deprecated' in the useSelected.tsx file
echo "Searching for deprecated comments in useSelected.tsx..."
rg "deprecated" packages/desktop-client/src/hooks/useSelected.tsx

Length of output: 606

@@ -238,7 +240,7 @@ export function ImportTransactionsModal({ options }) {
break;
}

const category_id = parseCategoryFields(trans, categories.list);
const category_id = parseCategoryFields(trans, categories);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider optimizing category lookup performance.

The current implementation of parseCategoryFields performs a linear search through categories multiple times. This could be inefficient for large category lists.

Consider creating a lookup map for better performance:

+const categoryMap = new Map(categories.map(cat => [cat.name, cat.id]));
+function parseCategoryFields(trans, categories) {
+  if (trans.category === undefined) return null;
+  if (categoryMap.has(trans.category)) {
+    return categoryMap.get(trans.category);
+  }
+  return null;
+}

This optimization would improve the performance of category lookups in both the preview and import phases.

Also applies to: 298-298, 587-587

@joel-jeremy joel-jeremy force-pushed the accounts-function-component branch 2 times, most recently from 09a0d84 to 9bfd9b1 Compare October 23, 2024 14:13
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: 6

🧹 Outside diff range and nitpick comments (8)
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (1)

75-82: Improve code documentation

The comment referencing a specific commit might become outdated or hard to track. Consider documenting the actual logic directly in the comment.

- // details of how the native app used to handle preview transactions here can be found at commit 05e58279
+ // Skip navigation for preview transactions (temporary transactions that haven't been saved yet)
packages/loot-core/src/shared/query.ts (1)

Line range hint 145-164: Consider handling additional edge cases in getPrimaryOrderBy.

The function could be more robust in handling malformed order expressions.

export function getPrimaryOrderBy(
  query: Query,
  defaultOrderBy: ObjectExpression | null,
) {
  const orderExprs = query.serialize().orderExpressions;
  if (orderExprs.length === 0) {
    if (defaultOrderBy) {
      return { order: 'asc', ...defaultOrderBy };
    }
    return null;
  }

  const firstOrder = orderExprs[0];
  if (typeof firstOrder === 'string') {
    return { field: firstOrder, order: 'asc' };
  }
+  // Handle empty object case
+  if (Object.keys(firstOrder).length === 0) {
+    return defaultOrderBy ? { order: 'asc', ...defaultOrderBy } : null;
+  }
  // Handle this form: { field: 'desc' }
  const [field] = Object.keys(firstOrder);
+  // Validate order value
+  const order = firstOrder[field];
+  if (typeof order !== 'string' || !['asc', 'desc'].includes(order)) {
+    return { field, order: 'asc' };
+  }
-  return { field, order: firstOrder[field] };
+  return { field, order };
}
packages/loot-core/src/client/data-hooks/schedules.tsx (1)

157-179: Consider improving type safety for accountId parameter.

While the implementation is correct, the type for accountId could be more precise.

Consider using a union type to better represent the possible values:

type AccountIdParam = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized';

export function accountSchedulesQuery(accountId?: AccountIdParam) {
  // ... rest of the implementation
}

This would:

  • Make the parameter type more explicit
  • Improve type checking
  • Better document the accepted values
packages/desktop-client/src/components/accounts/Balance.jsx (3)

74-76: Consider using a loading skeleton instead of returning null.

Returning null during loading might cause layout shifts. Consider showing a loading skeleton to maintain the component's dimensions and improve user experience.

-  if (isLoading) {
-    return null;
-  }
+  if (isLoading) {
+    return (
+      <DetailedBalance
+        name={t('Selected balance:')}
+        balance={0}
+        isLoading={true}
+      />
+    );
+  }

157-157: Remove commented out code.

These commented lines should be removed as they add noise to the codebase:

  • Line 157: // const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' });
  • Line 220: // transactionsQuery={transactionsQuery}

Also applies to: 220-220


Line range hint 176-216: Consider adding aria-label for better accessibility.

The balance button should have an aria-label to improve accessibility for screen readers.

 <ButtonWithLoading
   isLoading={!balanceQuery}
   data-testid="account-balance"
   variant="bare"
   onPress={onToggleExtraBalances}
+  aria-label={showExtraBalances ? t('Hide extra balances') : t('Show extra balances')}
   className={css({
     // ...
   })}
 >
packages/loot-core/src/client/query-helpers.ts (2)

235-238: Consider enhancing error logging.

The current error logging could be more informative for debugging purposes.

Apply this diff to improve error logging:

-    console.log('Error fetching data', e);
+    console.error('Error fetching data:', {
+      error: e,
+      query: this._query.serialize(),
+      requestId: this._inflightRequestId
+    });

420-420: Consider documenting the throttling behavior.

The use of once to prevent concurrent calls to fetchNext is a good practice, but it could be clearer to future maintainers.

Add a comment explaining the throttling:

+// Throttle fetchNext to prevent concurrent page fetches that could lead to race conditions
 fetchNext: () => Promise<void> = once(this._fetchNext);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ac752c6 and 9bfd9b1.

⛔ Files ignored due to path filters (2)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (43)
  • .eslintrc.js (1 hunks)
  • packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
  • packages/desktop-client/e2e/accounts.test.js (2 hunks)
  • packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
  • packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
  • packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
  • packages/loot-core/src/client/queries.ts (2 hunks)
  • packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
  • packages/loot-core/src/client/query-hooks.ts (1 hunks)
  • packages/loot-core/src/shared/query.ts (9 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useSchedules.ts
🚧 Files skipped from review as they are similar to previous changes (29)
  • .eslintrc.js
  • packages/desktop-client/e2e/accounts.mobile.test.js
  • packages/desktop-client/e2e/accounts.test.js
  • packages/desktop-client/e2e/page-models/account-page.js
  • packages/desktop-client/e2e/page-models/mobile-account-page.js
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js
  • packages/desktop-client/src/components/ManageRules.tsx
  • packages/desktop-client/src/components/accounts/Header.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useNotes.ts
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/client/data-hooks/dashboard.ts
  • packages/loot-core/src/client/data-hooks/filters.ts
  • packages/loot-core/src/client/data-hooks/reports.ts
  • packages/loot-core/src/client/data-hooks/widget.ts
  • packages/loot-core/src/client/queries.ts
  • packages/loot-core/src/client/query-helpers.test.ts
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (4)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277
Timestamp: 2024-10-22T05:34:56.976Z
Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
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.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
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.
packages/desktop-client/src/components/schedules/index.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:33-35
Timestamp: 2024-10-22T05:32:57.033Z
Learning: In the project's React components, when using `dispatch` from `useDispatch` in `useCallback` or `useEffect` hooks, `dispatch` must be included in the dependency array due to the `react-hooks/exhaustive-deps` ESLint rule.
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:37-39
Timestamp: 2024-10-22T05:32:30.530Z
Learning: In our React function components, we should include `dispatch` in the dependency array of `useCallback` hooks to comply with ESLint rules, even though `dispatch` is a stable function.
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:26-31
Timestamp: 2024-10-22T05:32:55.520Z
Learning: In this codebase, ESLint requires `dispatch` from `useDispatch` to be included in the dependency array of `useCallback` hooks, even though `dispatch` is stable.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/loot-core/src/client/query-helpers.ts

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🪛 GitHub Check: lint
packages/loot-core/src/client/data-hooks/transactions.ts

[warning] 226-226:
React Hook useEffect has a missing dependency: 'updateSearchQuery'. Either include it or remove the dependency array

🔇 Additional comments (39)
packages/desktop-client/src/components/schedules/index.tsx (4)

1-1: LGTM! Clean migration to useDispatch

The migration from useActions to useDispatch follows Redux best practices, and the imports are well-organized.

Also applies to: 3-3, 5-6


26-31: LGTM! Proper useCallback implementation

The event handlers are correctly memoized with useCallback, and dispatch is properly included in dependency arrays as per project guidelines.

Also applies to: 33-35, 37-39


71-76: LGTM! Improved data fetching implementation

The changes improve the code by:

  • Memoizing the query to prevent unnecessary recreations
  • Using explicit loading state instead of null checks
  • Properly destructuring the hook results

112-112: LGTM! Proper loading state handling

The loading state is correctly passed to the SchedulesTable component, improving the user experience during data fetching.

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (4)

1-8: LGTM! Clean import organization

The imports are well-structured and properly separated by source.


69-73: LGTM! Clean search implementation

The search functionality is well-implemented using the new hook pattern.


Line range hint 1-116: Well-executed migration to hooks!

The component has been successfully migrated to use hooks while maintaining all functionality. The code is clean, well-structured, and follows React best practices.


40-47: 🛠️ Refactor suggestion

Consider adding error handling for the transactions hook

While the hook implementation is clean, it would be beneficial to handle potential error states from the useTransactions hook to provide feedback to users when transaction loading fails.

Consider destructuring the error state and handling it in the UI:

 const {
   transactions,
   isLoading,
   loadMore: loadMoreTransactions,
   reload: reloadTransactions,
+  error,
 } = useTransactions({
   query: transactionsQuery,
 });
packages/loot-core/src/shared/query.ts (3)

Line range hint 25-40: LGTM! Constructor implementation is type-safe.

The use of WithRequired utility type ensures table is always provided, and all other fields have proper default values.


Line range hint 67-82: LGTM! Well-implemented type overloads in select.

The method handles various input types gracefully while maintaining type safety. The implementation properly normalizes inputs to arrays.


136-138: LGTM! Simple and effective reset implementation.

The method provides a clean way to reset the query while maintaining the table context.

packages/loot-core/src/client/data-hooks/schedules.tsx (3)

7-8: LGTM! Import and type definitions are well-structured.

The added imports and type definitions enhance type safety and properly support the new functionality.

Also applies to: 15-20


69-129: Well-implemented cleanup and unmount handling!

The implementation properly manages subscriptions and prevents memory leaks by:

  • Using refs to track query instances
  • Implementing proper cleanup on unmount
  • Checking mounted state before updates

Line range hint 134-156: LGTM! Well-structured context implementation.

The context implementation follows React best practices with:

  • Proper TypeScript types
  • Clean separation of concerns
  • Sensible default values
packages/desktop-client/src/components/accounts/Balance.jsx (1)

Line range hint 90-94: Verify the account balance calculation logic.

The balance calculation for scheduled transactions seems to invert the amount based on whether it matches the current account. Please verify this logic is correct for all transaction types (transfers, income, expenses).

✅ Verification successful

Let me gather more context about the transaction handling and balance calculation logic.

The above scripts will help us understand:

  1. How scheduled amounts are calculated
  2. The broader context of the balance calculation logic
  3. How transfers are handled in the codebase

This will help verify if the inversion logic is correct for different transaction types.


Let me gather one more piece of context to understand how scheduled transactions are structured and handled.

This will help us understand:

  1. How scheduled transactions are structured
  2. The full context of the Balance component
  3. The type definitions that might reveal the expected behavior

I now have enough context to provide a final response about the balance calculation logic.

The balance calculation logic for scheduled transactions is correct

The inversion of amounts based on account matching is intentional and correct because:

  1. The code is part of the SelectedBalance component that handles selected scheduled transactions (previews)
  2. The logic correctly handles transfers between accounts:
    • When accountId !== s._account (transfer from another account), it adds the amount to the balance
    • When accountId === s._account (transfer from current account), it subtracts the amount from the balance
  3. The getScheduledAmount function properly handles both simple amounts and range-based amounts (when _amountOp === 'isbetween')

This implementation ensures that transfers are reflected correctly in both the source and destination accounts' balances, maintaining proper double-entry accounting principles.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for similar account balance calculations in the codebase
rg -A 5 "scheduleBalance.*getScheduledAmount" 

Length of output: 702


Script:

#!/bin/bash
# Search for getScheduledAmount implementation
ast-grep --pattern 'function getScheduledAmount($_) { $$$ }'

# Search for broader context of balance calculations
rg -B 5 -A 10 "scheduleBalance.*getScheduledAmount"

# Look for transfer handling logic
rg -B 3 -A 3 "transfer.*amount|amount.*transfer" --type js

Length of output: 2843


Script:

#!/bin/bash
# Look for scheduled transaction type handling
rg -B 5 -A 5 "_account.*scheduleId|scheduleId.*_account" --type js

# Look for the component's full implementation to understand the context
rg -l "scheduleBalance" --type js | xargs cat

# Look for schedule-related types or interfaces
ast-grep --pattern 'interface $_ { $$$ _account $$$ }'

Length of output: 6430

packages/loot-core/src/client/data-hooks/transactions.ts (3)

86-92: 🛠️ Refactor suggestion

Consider wrapping callback functions to prevent undefined errors.

The reload and loadMore functions are directly exposed from pagedQueryRef.current, which could be null. To prevent potential runtime errors, consider wrapping these in functions that handle the null case.

   return {
     transactions,
     isLoading,
     error,
-    reload: pagedQueryRef.current?.run,
-    loadMore: pagedQueryRef.current?.fetchNext,
+    reload: () => pagedQueryRef.current?.run(),
+    loadMore: () => pagedQueryRef.current?.fetchNext(),
   };
⛔ Skipped due to learnings
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.

208-222: 🛠️ Refactor suggestion

Enhance search input handling and state management.

The search function could be more robust by:

  1. Properly handling undefined/null input
  2. Managing the searching state more effectively
   const updateSearchQuery = useMemo(
     () =>
       debounce((searchText: string) => {
+        const trimmedText = searchText?.trim() ?? '';
-        if (searchText === '') {
+        if (trimmedText === '') {
           resetQuery();
           setIsSearching(false);
-        } else if (searchText) {
+        } else {
           updateQuery(previousQuery =>
-            queries.transactionsSearch(previousQuery, searchText, dateFormat),
+            queries.transactionsSearch(previousQuery, trimmedText, dateFormat),
           );
           setIsSearching(true);
+          // Reset isSearching after the debounce delay
+          setTimeout(() => setIsSearching(false), delayMs);
         }
       }, delayMs),
     [dateFormat, delayMs, resetQuery, updateQuery],
   );

Likely invalid or redundant comment.


146-174: 🛠️ Refactor suggestion

Consider enhancing promise cleanup and error handling.

The Promise.all chain could benefit from:

  1. Using AbortController to properly cleanup pending operations
  2. More specific error handling to distinguish between different types of errors
+    const abortController = new AbortController();
 
     Promise.all(
       scheduleTransactions.map(transaction =>
-        send('rules-run', { transaction }),
+        send('rules-run', { transaction, signal: abortController.signal }),
       ),
     )
       .then(newTrans => {
         if (!isUnmounted) {
           // ... existing code ...
         }
       })
       .catch(error => {
-        if (!isUnmounted) {
+        if (!isUnmounted && error.name !== 'AbortError') {
           setError(error);
         }
       });
 
     return () => {
       isUnmounted = true;
+      abortController.abort();
     };

Likely invalid or redundant comment.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (5)

21-28: LGTM: Clean implementation of schedules query and hooks imports

The migration from useDefaultSchedulesQueryTransform to accountSchedulesQuery with useMemo is well-implemented. The new transaction hooks imports align with the PR's objective of migrating to functional components.

Also applies to: 59-62


Line range hint 106-155: LGTM: Well-structured callback implementations

The callbacks are properly memoized with correct dependency arrays, following React's best practices for performance optimization. The separation of concerns between different account actions (save, notes, close, reopen) is clear and maintainable.


217-222: LGTM: Enhanced type safety for accountId

The extension of the accountId type to include 'budgeted', 'offbudget', and 'uncategorized' improves type safety and aligns well with the broader changes in the codebase.


224-240: LGTM: Clean implementation of transactions query and state management

The transactions query implementation is well-structured with proper memoization and state management. The use of the new useTransactions hook aligns with the PR's objectives.


Line range hint 315-331: LGTM: Clean implementation of transaction display logic

The transaction display logic effectively handles both regular and preview transactions, with proper loading state management and clear prop passing to the TransactionListWithBalances component.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4)

39-40: LGTM: Type improvements enhance safety and functionality

The changes improve type safety by making the schedules array immutable and add loading state support for better UX.


207-207: LGTM: Consistent implementation of loading state and immutability

The changes properly implement the loading state and maintain immutability throughout the component.

Also applies to: 261-261, 406-406


278-343: LGTM: Good performance optimization with useCallback

The memoization of the render function helps prevent unnecessary re-renders.

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


345-375: LGTM: Effective memoization of renderItem function

The memoization with useCallback is properly implemented with the correct dependency array.

packages/loot-core/src/client/query-helpers.ts (3)

10-57: LGTM! Well-structured type definitions and API improvements.

The changes significantly enhance type safety and API usability through:

  • Generic type parameters for better type inference
  • Structured options objects for better maintainability
  • Clear and descriptive type definitions

61-61: ⚠️ Potential issue

Fix confusing type annotation.

The type () => void | null is confusing as it's unclear whether null applies to the function or its return value.

Apply this diff to fix the type annotation:

-private _unsubscribeSyncEvent: () => void | null;
+private _unsubscribeSyncEvent: (() => void) | null;

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)


Line range hint 383-417: Consider improving race condition handling in _fetchNext.

The current implementation has potential race conditions:

  1. State could change during the await at line 385
  2. The recursive call at line 400 could lead to stack overflow in edge cases

Let's verify if there are any reported issues related to this:

Consider this safer implementation:

 private _fetchNext = async () => {
+  const maxRetries = 3;
+  let retryCount = 0;
+
+  while (retryCount < maxRetries) {
     while (this.inflightRequestId) {
       await this._fetchDataPromise;
     }
 
     const previousData = this.data;
 
     if (!this._hasReachedEnd) {
       const { data } = await runQuery(
         this.query.limit(this._pageCount).offset(previousData.length),
       );
 
       if (this.inflightRequestId || previousData !== this.data) {
+        retryCount++;
-        return this._fetchNext();
+        continue;
       } else {
         if (data.length === 0) {
           this._hasReachedEnd = true;
         } else {
           // ... rest of the implementation
         }
+        return;
       }
     }
+    return;
   }
+  throw new Error('Max retries exceeded while fetching next page');
 };
packages/desktop-client/src/components/table.tsx (2)

819-819: Make isLoading optional in SelectedItemsButtonProps

Since you've provided a default value of false for isLoading in the function signature, you should make it optional in the type definition to maintain backward compatibility.

Apply this diff:

-  isLoading: boolean;
+  isLoading?: boolean;

Line range hint 842-856: LGTM! ButtonWithLoading implementation looks good.

The replacement of Button with ButtonWithLoading and the addition of the isLoading prop is well implemented. The default value ensures backward compatibility.

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)

769-776: LGTM! Improved loading state handling for schedules.

The changes properly handle the loading state of schedules by adding an early return, preventing potential null reference errors and optimizing performance.


Line range hint 1765-1775: LGTM! Added loading state prop to TransactionTableInner.

The component signature has been updated to include the isLoading prop, following React best practices for prop drilling and state management.


1994-1994: LGTM! Added loading state propagation to Table component.

The loading state is properly propagated to the Table component, maintaining React's unidirectional data flow.

packages/loot-core/src/client/query-hooks.ts (4)

7-11: Type UseQueryResult<Response> enhances type safety

The introduction of UseQueryResult<Response> improves clarity and type safety of the useQuery function's return value.


14-16: Updated useQuery signature increases flexibility

Allowing makeQuery to return Query | null provides flexibility for scenarios where a query might not be available, enhancing the hook's versatility.


23-25: State initialization aligns with query availability

Initializing data, isLoading, and error based on the query value ensures that the hook's state accurately reflects the query's availability.


56-56: Including error in the return value improves error handling

Returning the error state allows consumers of the useQuery hook to handle error conditions appropriately, enhancing robustness.

Comment on lines 41 to 69
const onAction = useCallback(
async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
switch (name) {
case 'post-transaction':
await send('schedule/post-transaction', { id });
break;
case 'skip':
await send('schedule/skip-next-date', { id });
break;
case 'complete':
await send('schedule/update', {
schedule: { id, completed: true },
});
break;
case 'restart':
await send('schedule/update', {
schedule: { id, completed: false },
resetNextDate: true,
});
break;
case 'delete':
await send('schedule/delete', { id });
break;
default:
throw new Error(`Unknown action: ${name}`);
}
},
[],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for API calls

The onAction callback makes several API calls but lacks error handling. Consider wrapping the API calls in try-catch blocks to handle potential failures gracefully.

Here's a suggested implementation:

 const onAction = useCallback(
   async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
+    try {
       switch (name) {
         case 'post-transaction':
           await send('schedule/post-transaction', { id });
           break;
         case 'skip':
           await send('schedule/skip-next-date', { id });
           break;
         case 'complete':
           await send('schedule/update', {
             schedule: { id, completed: true },
           });
           break;
         case 'restart':
           await send('schedule/update', {
             schedule: { id, completed: false },
             resetNextDate: true,
           });
           break;
         case 'delete':
           await send('schedule/delete', { id });
           break;
         default:
           throw new Error(`Unknown action: ${name}`);
       }
+    } catch (error) {
+      // Consider using a toast notification or error boundary
+      console.error(`Failed to perform ${name} action:`, error);
+    }
   },
   [],
 );

Committable suggestion was skipped due to low confidence.

Comment on lines 51 to 67
useEffect(() => {
function setup() {
return listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
if (
tables.includes('transactions') ||
tables.includes('category_mapping') ||
tables.includes('payee_mapping')
) {
paged.current?.run();
}

if (tables.includes('payees') || tables.includes('payee_mapping')) {
dispatch(getPayees());
}
return listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
if (
tables.includes('transactions') ||
tables.includes('category_mapping') ||
tables.includes('payee_mapping')
) {
reloadTransactions?.();
}
});
}

fetchTransactions();
return setup();
}, [dispatch, fetchTransactions]);

const updateSearchQuery = useDebounceCallback(
useCallback(
searchText => {
if (searchText === '' && currentQuery) {
updateQuery(currentQuery);
} else if (searchText && currentQuery) {
updateQuery(
queries.makeTransactionSearchQuery(
currentQuery,
searchText,
dateFormat,
),
);
if (tables.includes('payees') || tables.includes('payee_mapping')) {
dispatch(getPayees());
}
},
[currentQuery, dateFormat, updateQuery],
),
150,
}
});
}, [dispatch, reloadTransactions]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize sync event listener implementation

The sync event handler could be optimized to reduce unnecessary checks and array recreations.

Consider this optimization:

+ const TRANSACTION_RELATED_TABLES = ['transactions', 'category_mapping', 'payee_mapping'];
+ const PAYEE_RELATED_TABLES = ['payees', 'payee_mapping'];

  useEffect(() => {
    return listen('sync-event', ({ type, tables }) => {
      if (type === 'applied') {
-       if (
-         tables.includes('transactions') ||
-         tables.includes('category_mapping') ||
-         tables.includes('payee_mapping')
-       ) {
+       if (TRANSACTION_RELATED_TABLES.some(table => tables.includes(table))) {
          reloadTransactions?.();
        }

-       if (tables.includes('payees') || tables.includes('payee_mapping')) {
+       if (PAYEE_RELATED_TABLES.some(table => tables.includes(table))) {
          dispatch(getPayees());
        }
      }
    });
  }, [dispatch, reloadTransactions]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
function setup() {
return listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
if (
tables.includes('transactions') ||
tables.includes('category_mapping') ||
tables.includes('payee_mapping')
) {
paged.current?.run();
}
if (tables.includes('payees') || tables.includes('payee_mapping')) {
dispatch(getPayees());
}
return listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
if (
tables.includes('transactions') ||
tables.includes('category_mapping') ||
tables.includes('payee_mapping')
) {
reloadTransactions?.();
}
});
}
fetchTransactions();
return setup();
}, [dispatch, fetchTransactions]);
const updateSearchQuery = useDebounceCallback(
useCallback(
searchText => {
if (searchText === '' && currentQuery) {
updateQuery(currentQuery);
} else if (searchText && currentQuery) {
updateQuery(
queries.makeTransactionSearchQuery(
currentQuery,
searchText,
dateFormat,
),
);
if (tables.includes('payees') || tables.includes('payee_mapping')) {
dispatch(getPayees());
}
},
[currentQuery, dateFormat, updateQuery],
),
150,
}
});
}, [dispatch, reloadTransactions]);
const TRANSACTION_RELATED_TABLES = ['transactions', 'category_mapping', 'payee_mapping'];
const PAYEE_RELATED_TABLES = ['payees', 'payee_mapping'];
useEffect(() => {
return listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
if (TRANSACTION_RELATED_TABLES.some(table => tables.includes(table))) {
reloadTransactions?.();
}
if (PAYEE_RELATED_TABLES.some(table => tables.includes(table))) {
dispatch(getPayees());
}
}
});
}, [dispatch, reloadTransactions]);

Comment on lines 3 to 5
type ObjectExpression = {
[key: string]: ObjectExpression | unknown;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider making ObjectExpression more specific.

The current type definition allows for any unknown value, which might be too permissive. Consider defining more specific value types based on your query requirements to catch potential type errors at compile time.

type ObjectExpression = {
-  [key: string]: ObjectExpression | unknown;
+  [key: string]: ObjectExpression | string | number | boolean | null | undefined;
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type ObjectExpression = {
[key: string]: ObjectExpression | unknown;
};
type ObjectExpression = {
[key: string]: ObjectExpression | string | number | boolean | null | undefined;
};

Comment on lines 26 to 51
function loadStatuses(
schedules: readonly ScheduleEntity[],
onData: (data: ScheduleStatuses) => void,
onError: (error: Error) => void,
upcomingLength: string,
) {
return liveQuery<TransactionEntity>(getHasTransactionsQuery(schedules), {
onData: data => {
const hasTrans = new Set(data.filter(Boolean).map(row => row.schedule));

return new Map(
const scheduleStatuses = new Map(
schedules.map(s => [
s.id,
getStatus(s.next_date, s.completed, hasTrans.has(s.id), prefs),
getStatus(
s.next_date,
s.completed,
hasTrans.has(s.id),
upcomingLength,
),
]),
);
) as ScheduleStatuses;

onData?.(scheduleStatuses);
},
onError,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider optimizing the Map creation.

While the implementation is correct, the Map creation could be more efficient by avoiding the intermediate array creation.

Consider this optimization:

-      const scheduleStatuses = new Map(
-        schedules.map(s => [
-          s.id,
-          getStatus(
-            s.next_date,
-            s.completed,
-            hasTrans.has(s.id),
-            upcomingLength,
-          ),
-        ]),
-      ) as ScheduleStatuses;
+      const scheduleStatuses = new Map();
+      for (const s of schedules) {
+        scheduleStatuses.set(
+          s.id,
+          getStatus(
+            s.next_date,
+            s.completed,
+            hasTrans.has(s.id),
+            upcomingLength,
+          )
+        );
+      }

This approach:

  • Avoids creating an intermediate array
  • Reduces memory allocation
  • Maintains the same functionality
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function loadStatuses(
schedules: readonly ScheduleEntity[],
onData: (data: ScheduleStatuses) => void,
onError: (error: Error) => void,
upcomingLength: string,
) {
return liveQuery<TransactionEntity>(getHasTransactionsQuery(schedules), {
onData: data => {
const hasTrans = new Set(data.filter(Boolean).map(row => row.schedule));
return new Map(
const scheduleStatuses = new Map(
schedules.map(s => [
s.id,
getStatus(s.next_date, s.completed, hasTrans.has(s.id), prefs),
getStatus(
s.next_date,
s.completed,
hasTrans.has(s.id),
upcomingLength,
),
]),
);
) as ScheduleStatuses;
onData?.(scheduleStatuses);
},
onError,
});
function loadStatuses(
schedules: readonly ScheduleEntity[],
onData: (data: ScheduleStatuses) => void,
onError: (error: Error) => void,
upcomingLength: string,
) {
return liveQuery<TransactionEntity>(getHasTransactionsQuery(schedules), {
onData: data => {
const hasTrans = new Set(data.filter(Boolean).map(row => row.schedule));
const scheduleStatuses = new Map();
for (const s of schedules) {
scheduleStatuses.set(
s.id,
getStatus(
s.next_date,
s.completed,
hasTrans.has(s.id),
upcomingLength,
)
);
}
onData?.(scheduleStatuses);
},
onError,
});

Comment on lines +181 to +195
className={css({
paddingTop: 1,
paddingBottom: 1,
}}
[`& svg`]: {
width: 10,
height: 10,
marginLeft: 10,
color: theme.pillText,
transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)',
opacity: selectedItems.size > 0 || showExtraBalances ? 1 : 0,
},
[`&[data-hovered] svg`]: {
opacity: 1,
},
})}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extracting the CSS into a styled component.

The inline CSS-in-JS could be extracted into a styled component or a separate constant to improve readability and reusability.

const balanceButtonStyles = css({
  paddingTop: 1,
  paddingBottom: 1,
  '& svg': {
    width: 10,
    height: 10,
    marginLeft: 10,
    color: theme.pillText,
    transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)',
    opacity: selectedItems.size > 0 || showExtraBalances ? 1 : 0,
  },
  '&[data-hovered] svg': {
    opacity: 1,
  },
});

Then use it like:

<ButtonWithLoading
  className={balanceButtonStyles}
  // ... other props
>

Comment on lines 224 to 226
useEffect(() => {
return () => updateSearchQuery.cancel();
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix missing dependency in useEffect cleanup.

The cleanup effect is missing updateSearchQuery in its dependency array. While this might be intentional to avoid recreating the cleanup function, it's better to be explicit about dependencies.

   useEffect(() => {
     return () => updateSearchQuery.cancel();
-  }, []);
+  }, [updateSearchQuery]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
return () => updateSearchQuery.cancel();
}, []);
useEffect(() => {
return () => updateSearchQuery.cancel();
}, [updateSearchQuery]);
🧰 Tools
🪛 GitHub Check: lint

[warning] 226-226:
React Hook useEffect has a missing dependency: 'updateSearchQuery'. Either include it or remove the dependency array

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: 5

🧹 Outside diff range and nitpick comments (7)
packages/desktop-client/src/components/accounts/Balance.jsx (4)

74-76: Consider showing a loading state instead of returning null.

Returning null during loading might cause UI flicker. Consider showing a loading skeleton or placeholder to maintain UI stability.

-  if (isLoading) {
-    return null;
-  }
+  if (isLoading) {
+    return <DetailedBalance name={t('Selected balance:')} balance={0} isLoading={true} />;
+  }

Line range hint 90-94: Add comments explaining the balance calculation logic.

The conditional logic for adding/subtracting schedule amounts based on account ownership is not immediately obvious. Consider adding explanatory comments.

+      // Add to balance if it's a transfer from another account
+      // Subtract from balance if it's a transfer from this account
       if (accountId !== s._account) {
         scheduleBalance += getScheduledAmount(s._amount);
       } else {
         scheduleBalance -= getScheduledAmount(s._amount);
       }

117-124: Consider adding type safety to the component props.

The component could benefit from PropTypes or TypeScript type definitions to ensure proper usage.

FilteredBalance.propTypes = {
  filteredBalance: PropTypes.number
};

157-157: Remove commented code.

There are commented lines that should be removed if they're no longer needed:

  • Line 157: // const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' });
  • Line 220-221: // transactionsQuery={transactionsQuery}

Also applies to: 220-221

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)

406-406: LGTM! Consider enhancing loading state visibility

The loading state is properly implemented. Consider adding a loading indicator or skeleton rows to improve user experience during data fetching.

Would you like me to provide an example implementation of skeleton rows for the loading state?

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

789-792: Consider using Context for categories and splitting component responsibilities.

The component is handling multiple concerns and passing categories through multiple levels. Consider using React Context for categories and splitting the component into smaller, more focused components.

  1. Create a CategoryContext:
const CategoryContext = React.createContext();

export function CategoryProvider({ children }) {
  const { list: categories } = useCategories();
  return (
    <CategoryContext.Provider value={categories}>
      {children}
    </CategoryContext.Provider>
  );
}
  1. Split the component into smaller components:
  • ImportOptionsPanel
  • TransactionPreview
  • ImportControls

This would make the code more maintainable and reduce prop drilling.

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)

Line range hint 1765-1775: Consider adding a default value for the isLoading prop.

While the isLoading prop is correctly added, consider providing a default value to ensure predictable behavior when the prop is not passed.

function TransactionTableInner({
-  isLoading,
+  isLoading = false,
  tableNavigator,
  tableRef,
  listContainerRef,
  dateFormat = 'MM/dd/yyyy',
  newNavigator,
  renderEmpty,
  onScroll,
  ...props
})
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 9bfd9b1 and e32cc09.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (15)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/desktop-client/src/components/accounts/Header.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (1)
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (11)
packages/loot-core/src/client/data-hooks/transactions.ts (3)

1-30: LGTM! Well-structured imports and type definitions.

The imports are properly organized and the type definitions are comprehensive and well-structured.


234-240: LGTM! Well-implemented helper function.

The isForPreview function is focused, readable, and effectively determines if a schedule should be previewed based on its status.


86-92: 🛠️ Refactor suggestion

Consider wrapping callback functions to prevent undefined errors.

The reload and loadMore functions are directly exposed from pagedQueryRef.current, which could be null. This might cause runtime errors if consumers don't check for undefined.

   return {
     transactions,
     isLoading,
     error,
-    reload: pagedQueryRef.current?.run,
-    loadMore: pagedQueryRef.current?.fetchNext,
+    reload: () => pagedQueryRef.current?.run(),
+    loadMore: () => pagedQueryRef.current?.fetchNext(),
   };

Likely invalid or redundant comment.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4)

2-8: LGTM! Type safety improvements

The changes improve type safety by:

  1. Making the schedules array readonly to prevent accidental mutations
  2. Adding proper loading state management through the isLoading prop

Also applies to: 39-40


Line range hint 261-276: LGTM! Effective memoization and type safety

The memoization of items array with proper dependencies and readonly type prevents unnecessary re-renders while maintaining immutability.


278-343: LGTM! Effective memoization of render function

The renderSchedule function is properly memoized with useCallback and includes all necessary dependencies, which will help prevent unnecessary re-renders.

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


345-375: LGTM! Proper memoization of item renderer

The renderItem function is correctly memoized with appropriate dependencies, optimizing performance by preventing unnecessary re-renders.

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

819-819: ⚠️ Potential issue

Make isLoading optional in SelectedItemsButtonProps

The isLoading property is marked as required in the type definition, but it has a default value in the function implementation. This inconsistency could cause TypeScript errors in existing code that uses SelectedItemsButton without providing the isLoading prop.

Apply this diff to make the property optional:

-  isLoading: boolean;
+  isLoading?: boolean;

Likely invalid or redundant comment.


Line range hint 842-856: LGTM: ButtonWithLoading implementation

The replacement of Button with ButtonWithLoading and the addition of the isLoading prop is implemented correctly. The loading state is properly passed through to the new component.

Let's verify the usage of ButtonWithLoading across the codebase:

✅ Verification successful

Based on the search results, I can now generate the final response as I have sufficient information about the usage of ButtonWithLoading across the codebase.

The ButtonWithLoading component is consistently implemented with proper loading state handling

The usage of ButtonWithLoading across the codebase shows consistent implementation with the isLoading prop being properly passed through in all instances. The component is used in various parts of the application such as settings, modals, and forms, and in each case, it correctly handles the loading state. The implementation in the table component aligns with the established pattern throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for ButtonWithLoading usage to ensure consistent loading state handling
rg -A 2 "ButtonWithLoading"

Length of output: 23548

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)

769-776: LGTM! Well-implemented loading state handling.

The loading state check prevents accessing the schedules array before it's ready, avoiding potential runtime errors. This is a good defensive programming practice.


1994-1994: LGTM! Proper prop drilling implementation.

The loading state is correctly passed down to the Table component, maintaining consistency with the parent component's loading state.

Comment on lines +132 to +137
name: `balance-query-${accountId}-cleared`,
query: balanceQuery?.filter({ cleared: true }),
});
const uncleared = useSheetValue({
name: balanceQuery.name + '-uncleared',
query: balanceQuery.query.filter({ cleared: false }),
name: `balance-query-${accountId}-uncleared`,
query: balanceQuery?.filter({ cleared: false }),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing the filtered queries.

The filtered queries could be memoized to prevent unnecessary recalculations.

+  const clearedQuery = useMemo(
+    () => balanceQuery?.filter({ cleared: true }),
+    [balanceQuery]
+  );
+  const unclearedQuery = useMemo(
+    () => balanceQuery?.filter({ cleared: false }),
+    [balanceQuery]
+  );
   const cleared = useSheetValue({
     name: `balance-query-${accountId}-cleared`,
-    query: balanceQuery?.filter({ cleared: true }),
+    query: clearedQuery,
   });
   const uncleared = useSheetValue({
     name: `balance-query-${accountId}-uncleared`,
-    query: balanceQuery?.filter({ cleared: false }),
+    query: unclearedQuery,
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
name: `balance-query-${accountId}-cleared`,
query: balanceQuery?.filter({ cleared: true }),
});
const uncleared = useSheetValue({
name: balanceQuery.name + '-uncleared',
query: balanceQuery.query.filter({ cleared: false }),
name: `balance-query-${accountId}-uncleared`,
query: balanceQuery?.filter({ cleared: false }),
const clearedQuery = useMemo(
() => balanceQuery?.filter({ cleared: true }),
[balanceQuery]
);
const unclearedQuery = useMemo(
() => balanceQuery?.filter({ cleared: false }),
[balanceQuery]
);
const cleared = useSheetValue({
name: `balance-query-${accountId}-cleared`,
query: clearedQuery,
});
const uncleared = useSheetValue({
name: `balance-query-${accountId}-uncleared`,
query: unclearedQuery,
});

Comment on lines 114 to 136
const scheduleTransactions = useMemo(() => {
if (isSchedulesLoading) {
return [];
}

// Kick off an async rules application
const schedulesForPreview = schedules.filter(s =>
isForPreview(s, statuses),
);

return schedulesForPreview.map(schedule => ({
id: 'preview/' + schedule.id,
payee: schedule._payee,
account: schedule._account,
amount: schedule._amount,
date: schedule.next_date,
schedule: schedule.id,
}));
}, [isSchedulesLoading, schedules, statuses]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing schedule transformation for better performance.

The schedule transformation in the useMemo hook could be optimized by extracting the mapping function to avoid recreating it on every render.

+const transformScheduleToPreview = (schedule: ScheduleEntity) => ({
+  id: 'preview/' + schedule.id,
+  payee: schedule._payee,
+  account: schedule._account,
+  amount: schedule._amount,
+  date: schedule.next_date,
+  schedule: schedule.id,
+});

 const scheduleTransactions = useMemo(() => {
   if (isSchedulesLoading) {
     return [];
   }

   const schedulesForPreview = schedules.filter(s =>
     isForPreview(s, statuses),
   );

-  return schedulesForPreview.map(schedule => ({
-    id: 'preview/' + schedule.id,
-    payee: schedule._payee,
-    account: schedule._account,
-    amount: schedule._amount,
-    date: schedule.next_date,
-    schedule: schedule.id,
-  }));
+  return schedulesForPreview.map(transformScheduleToPreview);
 }, [isSchedulesLoading, schedules, statuses]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const scheduleTransactions = useMemo(() => {
if (isSchedulesLoading) {
return [];
}
// Kick off an async rules application
const schedulesForPreview = schedules.filter(s =>
isForPreview(s, statuses),
);
return schedulesForPreview.map(schedule => ({
id: 'preview/' + schedule.id,
payee: schedule._payee,
account: schedule._account,
amount: schedule._amount,
date: schedule.next_date,
schedule: schedule.id,
}));
}, [isSchedulesLoading, schedules, statuses]);
const transformScheduleToPreview = (schedule: ScheduleEntity) => ({
id: 'preview/' + schedule.id,
payee: schedule._payee,
account: schedule._account,
amount: schedule._amount,
date: schedule.next_date,
schedule: schedule.id,
});
const scheduleTransactions = useMemo(() => {
if (isSchedulesLoading) {
return [];
}
// Kick off an async rules application
const schedulesForPreview = schedules.filter(s =>
isForPreview(s, statuses),
);
return schedulesForPreview.map(transformScheduleToPreview);
}, [isSchedulesLoading, schedules, statuses]);

@@ -308,7 +310,7 @@ export function ImportTransactionsModal({ options }) {
return next;
}, []);
},
[accountId, categories.list, clearOnImport, importPreviewTransactions],
[accountId, categories, clearOnImport, importPreviewTransactions],
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing processed transactions and caching category lookups.

The dependency array includes categories which could cause unnecessary re-renders of preview transactions. Consider memoizing the category lookup and processed transactions.

// Add at the top of the component
const categoryMap = useMemo(
  () => new Map(categories.map(cat => [cat.name, cat.id])),
  [categories]
);

// Use in parseCategoryFields
const parseCategoryFields = useCallback((trans) => {
  if (!trans.category) return null;
  return categoryMap.get(trans.category) || trans.category;
}, [categoryMap]);

Comment on lines +587 to 589
const category_id = parseCategoryFields(trans, categories);
trans.category = category_id;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling for category processing.

The category processing lacks proper validation and error handling. Consider adding validation and more descriptive error messages.

-      const category_id = parseCategoryFields(trans, categories);
-      trans.category = category_id;
+      try {
+        if (!Array.isArray(categories)) {
+          throw new Error('Categories must be an array');
+        }
+        const category_id = parseCategoryFields(trans, categories);
+        trans.category = category_id;
+      } catch (error) {
+        console.error('Failed to process category:', error);
+        errorMessage = `Failed to process category for transaction on ${trans.date}: ${error.message}`;
+        break;
+      }

Committable suggestion was skipped due to low confidence.

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: 9

🧹 Outside diff range and nitpick comments (11)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)

Line range hint 33-45: Address the type coercion TODO comment.

The current type coercion (schedule._payee as unknown as string) is a potential source of runtime errors. This should be properly typed to ensure type safety.

Consider refactoring to use proper typing:

-      describe={schedule =>
-        describeSchedule(schedule, byId[schedule._payee as unknown as string])
-      }
+      describe={schedule => {
+        if (typeof schedule._payee !== 'string') {
+          console.warn('Unexpected payee type:', schedule._payee);
+          return 'Invalid schedule';
+        }
+        return describeSchedule(schedule, byId[schedule._payee]);
+      }}

Also, consider updating the ScheduleEntity type to properly type the _payee field.

packages/loot-core/src/client/query-hooks.ts (1)

14-57: Consider adding tests for error scenarios

Since this hook is critical for the Account component migration and handles error states, consider adding comprehensive tests covering:

  • Null query handling
  • Error propagation
  • Cleanup on unmount
  • State transitions
packages/loot-core/src/shared/query.ts (2)

136-138: Consider adding JSDoc documentation for the reset method.

The reset method's purpose and behavior could be clearer with documentation explaining that it creates a fresh query instance for the same table.

+/**
+ * Resets the query to its initial state while preserving the table.
+ * @returns {Query} A new Query instance with only the table property set.
+ */
 reset() {
   return q(this.state.table);
 }

Line range hint 145-164: Consider improving type safety in getPrimaryOrderBy.

The function could benefit from better type narrowing and validation of order expressions.

 export function getPrimaryOrderBy(
   query: Query,
   defaultOrderBy: ObjectExpression | null,
 ) {
   const orderExprs = query.serialize().orderExpressions;
   if (orderExprs.length === 0) {
     if (defaultOrderBy) {
       return { order: 'asc' as const, ...defaultOrderBy };
     }
     return null;
   }

   const firstOrder = orderExprs[0];
+  type OrderDirection = 'asc' | 'desc';
+  
   if (typeof firstOrder === 'string') {
-    return { field: firstOrder, order: 'asc' };
+    return { field: firstOrder, order: 'asc' as const };
   }
   
   // Handle this form: { field: 'desc' }
   const [field] = Object.keys(firstOrder);
+  const order = firstOrder[field];
+  
+  if (typeof order !== 'string' || !['asc', 'desc'].includes(order)) {
+    return { field, order: 'asc' as const };
+  }
   
-  return { field, order: firstOrder[field] };
+  return { field, order: order as OrderDirection };
 }
packages/desktop-client/src/components/accounts/Balance.jsx (3)

72-76: Consider showing a loading indicator instead of null.

Early return with null during loading might cause layout shifts. Consider showing a loading skeleton or placeholder to maintain visual stability.

- if (isLoading) {
-   return null;
- }
+ if (isLoading) {
+   return <DetailedBalance name={t('Selected balance:')} balance={0} isLoading={true} />;
+ }

Line range hint 90-94: Improve variable naming for clarity.

The condition accountId !== s._account could be more explicit about what it's comparing. Consider renaming variables or adding a comment to clarify the logic.

- if (accountId !== s._account) {
+ // Add/subtract based on whether the schedule is for the current account
+ const isExternalAccount = accountId !== s._account;
+ if (isExternalAccount) {

117-124: Add PropTypes for type safety.

Consider adding PropTypes or TypeScript types to validate the filteredBalance prop and document its expected type.

FilteredBalance.propTypes = {
  filteredBalance: PropTypes.number
};
packages/loot-core/src/client/query-helpers.ts (2)

186-191: Consider adding validation in optimistic update.

The _optimisticUpdate method directly updates data without validation. Consider adding validation to ensure the update function doesn't produce invalid data states.

 protected _optimisticUpdate = (
   updateFn: (data: Data<TResponse>) => Data<TResponse>,
 ) => {
   const previousData = this.data;
+  const newData = updateFn(this.data);
+  if (newData === undefined || newData === null) {
+    console.warn('Optimistic update produced invalid data state');
+    return;
+  }
-  this.updateData(updateFn);
+  this.updateData(() => newData);
   this.onData(this.data, previousData);
 };

Line range hint 383-417: Potential deadlock in fetchNext implementation.

The while loop waiting for in-flight requests could potentially deadlock if an error occurs during this._fetchDataPromise execution. Consider adding a timeout or error handling mechanism.

 private _fetchNext = async () => {
+  const timeout = 30000; // 30 seconds
+  const startTime = Date.now();
   while (this.inflightRequestId) {
+    if (Date.now() - startTime > timeout) {
+      throw new Error('Timeout waiting for in-flight request');
+    }
     await this._fetchDataPromise;
   }
   // ... rest of the implementation
 };
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (1)

789-792: Add prop validation for categories in Transaction component.

The categories array is passed directly to the Transaction component without validation. Consider adding PropTypes or TypeScript types to ensure type safety.

Transaction.propTypes = {
  categories: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired
    })
  ).isRequired,
  // ... other props
};
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)

Line range hint 1765-1775: Consider adding prop documentation and type definitions.

The loading state propagation is implemented correctly, but could benefit from additional documentation and type safety:

  1. Add JSDoc or comments explaining the purpose of the isLoading prop
  2. Consider adding PropTypes or TypeScript definitions for the prop

Example documentation:

/**
 * @param {Object} props
 * @param {boolean} props.isLoading - Indicates whether the transaction data is currently being fetched
 */

Example PropTypes:

TransactionTableInner.propTypes = {
  isLoading: PropTypes.bool,
  // ... other props
};

Also applies to: 1994-1994

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e32cc09 and 11e9226.

⛔ Files ignored due to path filters (2)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (44)
  • .eslintrc.js (1 hunks)
  • packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
  • packages/desktop-client/e2e/accounts.test.js (2 hunks)
  • packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
  • packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (2 hunks)
  • packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
  • packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (3 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
  • packages/loot-core/src/client/queries.ts (2 hunks)
  • packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
  • packages/loot-core/src/client/query-hooks.ts (1 hunks)
  • packages/loot-core/src/shared/query.ts (9 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useSchedules.ts
🚧 Files skipped from review as they are similar to previous changes (29)
  • .eslintrc.js
  • packages/desktop-client/e2e/accounts.mobile.test.js
  • packages/desktop-client/e2e/accounts.test.js
  • packages/desktop-client/e2e/page-models/account-page.js
  • packages/desktop-client/e2e/page-models/mobile-account-page.js
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js
  • packages/desktop-client/src/components/ManageRules.tsx
  • packages/desktop-client/src/components/accounts/Header.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/schedules/index.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/table.tsx
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useNotes.ts
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/client/data-hooks/dashboard.ts
  • packages/loot-core/src/client/data-hooks/filters.ts
  • packages/loot-core/src/client/data-hooks/reports.ts
  • packages/loot-core/src/client/data-hooks/widget.ts
  • packages/loot-core/src/client/queries.ts
  • packages/loot-core/src/client/query-helpers.test.ts
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (3)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277
Timestamp: 2024-10-22T05:34:56.976Z
Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
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.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
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.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/loot-core/src/client/query-helpers.ts

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🔇 Additional comments (36)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)

25-31: LGTM: Well-implemented loading state with accessibility.

The loading state implementation follows best practices with proper accessibility labeling and visual feedback.

packages/loot-core/src/client/query-hooks.ts (6)

7-11: LGTM: Well-structured type definition with proper immutability

The UseQueryResult type provides a clear contract with immutable data and proper error handling support.


14-16: LGTM: Improved signature with nullable query support

The updated signature with Query | null return type adds flexibility while maintaining type safety.


17-21: LGTM: Proper memoization with clear documentation

The memoization strategy is well-implemented and the ESLint disable is properly documented.


23-25: LGTM: Clean state management with proper typing

The state management is well-structured with appropriate initial values and TypeScript types.


35-38: Consider using useRef for unmount flag

The previous review comment about using useRef for the isUnmounted flag is still valid to prevent potential stale closure issues.


52-57: LGTM: Complete return value matching type definition

The return value properly includes all necessary states as defined in UseQueryResult.

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (5)

1-8: LGTM! Clean hook imports and usage.

The migration to hooks is well-structured with proper imports from the new data-hooks directory.


28-34: Skip comment - existing review is still valid.

The previous review comment about memoizing the filter object is still applicable to this code segment.


37-47: LGTM! Clean implementation of transaction state management.

The component effectively utilizes the new useTransactions hook, properly managing transaction state and related functionalities.


51-67: Skip comment - existing review is still valid.

The previous review comment about optimizing the sync event listener implementation is still applicable to this code segment.


75-82: LGTM! Well-documented transaction handler.

The implementation is clean and the comment provides valuable context about preview transaction handling.

packages/loot-core/src/shared/query.ts (2)

8-13: LGTM! Well-structured type definitions for QueryState.

The type definitions are clear and properly constrained, making good use of union types for expressions.


Line range hint 25-40: LGTM! Well-implemented constructor with proper type safety.

Good use of WithRequired to enforce the table property while keeping other properties optional. The defaults are properly initialized before the spread operator.

packages/desktop-client/src/components/accounts/Balance.jsx (1)

158-165: LGTM! Good use of useMemo.

The memoization is correctly implemented with appropriate dependencies, which will help prevent unnecessary recalculations.

packages/loot-core/src/client/data-hooks/transactions.ts (2)

229-231: ⚠️ Potential issue

Fix missing dependency in useEffect cleanup.

The cleanup effect is missing updateSearchQuery in its dependency array.

Apply this diff to fix the dependency array:

   useEffect(() => {
     return () => updateSearchQuery.cancel();
-  }, []);
+  }, [updateSearchQuery]);

Likely invalid or redundant comment.


213-227: ⚠️ Potential issue

Enhance search input handling and state management.

The current implementation has several issues:

  1. Search function doesn't handle undefined/null input
  2. isSearching state remains true even after search completes
  3. Missing proper input sanitization

Apply this diff to improve the implementation:

   const updateSearchQuery = useMemo(
     () =>
       debounce((searchText: string) => {
+        const trimmedText = searchText?.trim() ?? '';
-        if (searchText === '') {
+        if (trimmedText === '') {
           resetQuery();
           setIsSearching(false);
-        } else if (searchText) {
+        } else {
           updateQuery(previousQuery =>
-            queries.transactionsSearch(previousQuery, searchText, dateFormat),
+            queries.transactionsSearch(previousQuery, trimmedText, dateFormat),
           );
           setIsSearching(true);
+          // Reset isSearching after the debounce delay
+          setTimeout(() => setIsSearching(false), delayMs);
         }
       }, delayMs),
     [dateFormat, delayMs, resetQuery, updateQuery],
   );

Likely invalid or redundant comment.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (5)

21-28: LGTM: Clean import organization for new hooks

The new imports for useTransactions, useTransactionsSearch, and usePreviewTransactions align well with the PR's objective of migrating to functional components with hooks.


59-62: LGTM: Proper memoization of schedules query

The useMemo implementation correctly memoizes the schedules query with appropriate dependency on accountId.


Line range hint 106-155: LGTM: Well-structured callback implementations

The callbacks are properly memoized using useCallback with correct dependency arrays, following React best practices for performance optimization.


217-222: LGTM: Improved type safety with precise type definitions

The enhanced type definitions for accountId and accountName provide better type safety and clarity using proper union types and AccountEntity references.


224-240: 🛠️ Refactor suggestion

Consider memoizing the options object in baseTransactionsQuery

While the current implementation is correct, consider memoizing the options object to prevent unnecessary re-renders:

 const baseTransactionsQuery = useCallback(
-  () =>
-    queries.transactions(accountId).options({ splits: 'none' }).select('*'),
+  () => {
+    const options = useMemo(() => ({ splits: 'none' }), []);
+    return queries.transactions(accountId).options(options).select('*');
+  },
   [accountId],
 );

Likely invalid or redundant comment.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (5)

2-8: LGTM: Type safety improvements

Good improvements in type safety:

  • Added necessary hooks for functional component migration
  • Made schedules prop readonly to enforce immutability

Also applies to: 39-40


207-207: LGTM: Proper loading state handling

Good improvements:

  • Added loading state with sensible default
  • Consistent use of readonly type for items

Also applies to: 261-261


278-343: LGTM: Performance optimization with useCallback

Good use of useCallback for memoizing render functions with proper dependency arrays.

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


325-327: Simplify nested condition using optional chaining

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


406-406: LGTM: Proper loading state propagation

Loading state is correctly passed to the Table component.

packages/loot-core/src/client/query-helpers.ts (2)

10-58: Well-structured type definitions and function signatures!

The introduction of TypeScript types and generics significantly improves type safety and API clarity. The structured parameter objects make the API more maintainable and self-documenting.


422-425: LGTM: Optimistic update with count adjustment.

The optimistic update implementation correctly adjusts the total count based on the data length difference, maintaining data consistency.

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

243-246: Previous performance optimization comment is still applicable.

The linear search implementation in parseCategoryFields remains unchanged.


587-588: ⚠️ Potential issue

Improve category assignment error handling.

The current implementation assigns category_id to trans.category without validation. This could lead to null categories being assigned when they shouldn't be.

-const category_id = parseCategoryFields(trans, categories);
-trans.category = category_id;
+const category_id = parseCategoryFields(trans, categories);
+if (trans.category && !category_id) {
+  errorMessage = `Unable to find matching category for "${trans.category}"`;
+  break;
+}
+trans.category = category_id;

Likely invalid or redundant comment.

packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (2)

8-8: LGTM: Clean import addition for SchedulesProvider

The import statement is correctly placed and follows the existing import organization pattern.


149-171: LGTM: Clean integration of SchedulesProvider

The SchedulesProvider is correctly integrated into the component hierarchy:

  • Maintains proper nesting order
  • Preserves all existing props and child components
  • Follows the established component composition pattern

Let's verify that the SchedulesProvider is consistently used across other test files:

✅ Verification successful

SchedulesProvider usage is consistent across the codebase

The verification shows that SchedulesProvider is consistently used across the codebase:

  • In TransactionsTable.test.jsx, it's correctly wrapped around the transaction table components
  • In production code, it's properly used in Account.tsx and AccountTransactions.tsx with the required query prop
  • The provider's implementation in schedules.tsx confirms it's designed to be used as shown in the test file
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent usage of SchedulesProvider in test files
# Look for other test files that might need the same wrapper

# Search for test files using TransactionTable
fd -e test.jsx -e test.js | xargs rg "TransactionTable"

# Check for existing SchedulesProvider usage
rg "SchedulesProvider" -A 2 -B 2

Length of output: 6433

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)

769-776: Great improvement to loading state handling!

The changes effectively prevent potential null reference errors and optimize performance by:

  1. Using the useCachedSchedules hook to manage loading state
  2. Adding an early return during loading
  3. Moving the schedule lookup after the loading check
packages/loot-core/src/client/data-hooks/schedules.tsx (2)

89-94: Good practice: Preventing state updates after unmount

The implementation of onError now correctly checks isUnmounted before updating the state, which prevents potential memory leaks caused by setting state after the component has unmounted.


101-125: Proper cleanup of subscriptions

The cleanup function in the useEffect hook properly unsubscribes from scheduleQueryRef.current and statusQueryRef.current. This ensures that all subscriptions are terminated when the component unmounts, preventing memory leaks.

Comment on lines 22 to 23
const schedulesQuery = useMemo(() => q('schedules').select('*'), []);
const { schedules = [], isLoading } = useSchedules({ query: schedulesQuery });
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider optimizing the schedules query.

The current query selects all fields (*) from schedules. Consider selecting only the required fields to optimize data transfer and processing.

-  const schedulesQuery = useMemo(() => q('schedules').select('*'), []);
+  const schedulesQuery = useMemo(
+    () => q('schedules').select(['id', '_payee', 'name', /* other required fields */]),
+    []
+  );

Committable suggestion was skipped due to low confidence.

Comment on lines 69 to 73
const { search: onSearch } = useTransactionsSearch({
updateQuery: setTransactionsQuery,
resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
dateFormat,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing the resetQuery callback.

The resetQuery callback could be memoized to prevent unnecessary recreations.

+ const resetQuery = useCallback(
+   () => setTransactionsQuery(baseTransactionsQuery()),
+   [baseTransactionsQuery]
+ );

  const { search: onSearch } = useTransactionsSearch({
    updateQuery: setTransactionsQuery,
-   resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
+   resetQuery,
    dateFormat,
  });

Committable suggestion was skipped due to low confidence.

Comment on lines +217 to 229
{showExtraBalances && balanceQuery && (
<MoreBalances
accountId={accountId}
// transactionsQuery={transactionsQuery}
balanceQuery={balanceQuery}
/>
</Button>
{showExtraBalances && <MoreBalances balanceQuery={balanceQuery} />}

)}
{selectedItems.size > 0 && (
<SelectedBalance selectedItems={selectedItems} account={account} />
<SelectedBalance selectedItems={selectedItems} accountId={accountId} />
)}
{showFilteredBalance && (
<FilteredBalance filteredBalance={filteredBalance} />
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extracting balance display logic into a separate component.

The conditional rendering logic could be simplified by extracting it into a dedicated component, improving readability and maintainability.

function BalanceDisplays({ 
  showExtraBalances, 
  balanceQuery, 
  accountId, 
  selectedItems, 
  showFilteredBalance, 
  filteredBalance 
}) {
  return (
    <>
      {showExtraBalances && balanceQuery && (
        <MoreBalances accountId={accountId} balanceQuery={balanceQuery} />
      )}
      {selectedItems.size > 0 && (
        <SelectedBalance selectedItems={selectedItems} accountId={accountId} />
      )}
      {showFilteredBalance && (
        <FilteredBalance filteredBalance={filteredBalance} />
      )}
    </>
  );
}

Comment on lines 94 to 96
reload: pagedQueryRef.current?.run,
loadMore: pagedQueryRef.current?.fetchNext,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Provide safe no-op functions for reload and loadMore.

The current implementation could return undefined functions when pagedQueryRef.current is null, which might cause runtime errors.

Apply this diff to provide safe no-op functions:

-    reload: pagedQueryRef.current?.run,
-    loadMore: pagedQueryRef.current?.fetchNext,
+    reload: () => pagedQueryRef.current?.run(),
+    loadMore: () => pagedQueryRef.current?.fetchNext(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
reload: pagedQueryRef.current?.run,
loadMore: pagedQueryRef.current?.fetchNext,
};
reload: () => pagedQueryRef.current?.run(),
loadMore: () => pagedQueryRef.current?.fetchNext(),
};

return;
}

setIsLoading(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve loading state management.

The loading state is set to true before validating the query, which could lead to unnecessary loading states. Consider moving this after the query validation.

Apply this diff to fix the loading state management:

-    setIsLoading(true);
     if (query.state.table !== 'transactions') {
       onError(new Error('Query must be a transactions query.'));
       return;
     }
+    setIsLoading(true);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setIsLoading(true);
if (query.state.table !== 'transactions') {
onError(new Error('Query must be a transactions query.'));
return;
}
setIsLoading(true);

Comment on lines 150 to 154
Promise.all(
scheduleTransactions.map(transaction =>
send('rules-run', { transaction }),
),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add cleanup for pending promises.

The Promise.all chain could continue executing after component unmount. While state updates are protected, the rules-run operations will still complete unnecessarily.

Apply this diff to add cleanup:

+    const abortController = new AbortController();
     Promise.all(
       scheduleTransactions.map(transaction =>
-        send('rules-run', { transaction }),
+        send('rules-run', { transaction, signal: abortController.signal }),
       ),
     )
       .then(newTrans => {
         if (!isUnmounted) {
           // ... existing code ...
         }
       })
       .catch(error => {
-        if (!isUnmounted) {
+        if (!isUnmounted && error.name !== 'AbortError') {
           setError(error);
           setIsLoading(false);
         }
       });

     return () => {
       isUnmounted = true;
+      abortController.abort();
     };

Committable suggestion was skipped due to low confidence.

Comment on lines 315 to 318
const transactionsToDisplay = !isSearching
? previewTransactions.concat(transactions)
: transactions;

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing transactionsToDisplay

The transaction concatenation operation could be expensive for large lists. Consider memoizing the result:

- const transactionsToDisplay = !isSearching
-   ? previewTransactions.concat(transactions)
-   : transactions;

+ const transactionsToDisplay = useMemo(
+   () => (!isSearching ? previewTransactions.concat(transactions) : transactions),
+   [isSearching, previewTransactions, transactions]
+ );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const transactionsToDisplay = !isSearching
? previewTransactions.concat(transactions)
: transactions;
const transactionsToDisplay = useMemo(
() => (!isSearching ? previewTransactions.concat(transactions) : transactions),
[isSearching, previewTransactions, transactions]
);

Comment on lines 345 to 375
const renderItem = useCallback(
({ item }: { item: SchedulesTableItem }) => {
if (item.id === 'show-completed') {
return (
<Row
height={ROW_HEIGHT}
inset={15}
style={{
cursor: 'pointer',
backgroundColor: 'transparent',
':hover': { backgroundColor: theme.tableRowBackgroundHover },
}}
onClick={() => setShowCompleted(true)}
>
<Field
width="flex"
style={{
fontStyle: 'italic',
textAlign: 'center',
color: theme.tableText,
}}
>
<Trans>Show completed schedules</Trans>
</Field>
</Row>
);
}
return renderSchedule({ schedule: item as ScheduleEntity });
},
[renderSchedule],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using a type predicate for safer type narrowing

Instead of using type casting, consider using a type predicate for better type safety.

function isScheduleEntity(item: SchedulesTableItem): item is ScheduleEntity {
  return 'completed' in item;
}

// Then in renderItem:
return isScheduleEntity(item) ? renderSchedule({ schedule: item }) : null;

@@ -5,6 +5,7 @@ import deepEqual from 'deep-equal';
import { amountToInteger } from 'loot-core/src/shared/util';

import { useActions } from '../../../hooks/useActions';
import { useCategories } from '../../../hooks/useCategories';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing categories to optimize performance.

The categories list from useCategories hook is used in multiple callback dependencies. Consider memoizing it to prevent unnecessary re-renders of the preview and import logic.

-const { list: categories } = useCategories();
+const { list: categoriesList } = useCategories();
+const categories = useMemo(() => categoriesList, [categoriesList]);

Also applies to: 159-159

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: 5

🧹 Outside diff range and nitpick comments (15)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)

25-31: Consider extracting the loading indicator into a reusable styled component.

While the loading state handling is good, the inline styles could be moved to a styled component for better maintainability and reuse.

+const LoadingContainer = styled(View)`
+  display: inline-flex;
+`;

 if (isLoading) {
   return (
-    <View aria-label="Loading..." style={{ display: 'inline-flex' }}>
+    <LoadingContainer aria-label="Loading...">
       <AnimatedLoading width={10} height={10} />
-    </View>
+    </LoadingContainer>
   );
 }
packages/desktop-client/src/components/schedules/index.tsx (1)

41-69: Add dispatch to onAction dependencies.

The onAction callback should include dispatch in its dependency array for consistency with other callbacks and to comply with the project's conventions, even though it's not currently using dispatch.

   },
-  [],
+  [dispatch],
 );
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (1)

75-82: Improve the code comment about preview transactions.

The comment referencing a specific commit hash could be more descriptive. Consider adding a brief explanation of what preview transactions are and why they need special handling.

-      // details of how the native app used to handle preview transactions here can be found at commit 05e58279
+      // Preview transactions (temporary transactions not yet saved) are handled differently in native apps
+      // and should not navigate to the transaction details page. See commit 05e58279 for historical context.
packages/loot-core/src/shared/query.ts (1)

Line range hint 145-164: Consider validating order values in getPrimaryOrderBy.

The function assumes string orders are always 'asc' and doesn't validate order values. Consider adding validation to ensure order values are either 'asc' or 'desc'.

export function getPrimaryOrderBy(
  query: Query,
  defaultOrderBy: ObjectExpression | null,
) {
+ type OrderDirection = 'asc' | 'desc';
+ const isValidOrder = (order: unknown): order is OrderDirection =>
+   order === 'asc' || order === 'desc';

  const orderExprs = query.serialize().orderExpressions;
  if (orderExprs.length === 0) {
    if (defaultOrderBy) {
+     const order = defaultOrderBy.order;
+     if (!isValidOrder(order)) {
+       throw new Error(`Invalid order: ${order}`);
+     }
      return { order: 'asc', ...defaultOrderBy };
    }
    return null;
  }

  const firstOrder = orderExprs[0];
  if (typeof firstOrder === 'string') {
    return { field: firstOrder, order: 'asc' };
  }
  const [field] = Object.keys(firstOrder);
+ const order = firstOrder[field];
+ if (!isValidOrder(order)) {
+   throw new Error(`Invalid order: ${order}`);
+ }
- return { field, order: firstOrder[field] };
+ return { field, order };
}
packages/desktop-client/src/components/accounts/Balance.jsx (2)

72-76: Consider adding a loading indicator for better user experience.

While returning null during loading is a valid approach, users might benefit from seeing a loading state. Consider showing a skeleton loader or spinner during the loading phase.

 const { isLoading, schedules = [] } = useCachedSchedules();

 if (isLoading) {
-  return null;
+  return <LoadingSpinner size="small" />;
 }

Line range hint 90-94: Add comments explaining the account balance adjustment logic.

The account comparison and subsequent balance adjustment (positive vs negative) contain important business logic that should be documented for future maintenance.

+      // Add to balance if it's a transfer to another account
+      // Subtract from balance if it's a transaction in this account
       if (accountId !== s._account) {
         scheduleBalance += getScheduledAmount(s._amount);
       } else {
         scheduleBalance -= getScheduledAmount(s._amount);
       }
packages/loot-core/src/client/data-hooks/transactions.ts (1)

105-191: Consider adding error boundary protection.

The hook handles errors well internally, but consider wrapping the component using this hook with an error boundary to gracefully handle any unhandled errors during schedule processing.

packages/desktop-client/src/components/transactions/TransactionList.jsx (2)

Line range hint 100-117: LGTM: Smart refresh optimization

The conditional refresh logic based on date changes is well thought out. It optimizes performance by only triggering a full refresh when the transaction order might change.

Consider extracting the timestamp generation to a constant for better readability:

- changes.diff.updated[0].sort_order = Date.now();
+ const currentTimestamp = Date.now();
+ changes.diff.updated[0].sort_order = currentTimestamp;

Line range hint 200-243: Consider reducing prop drilling

The TransactionTable component receives a large number of props. While this works, it could make the component harder to maintain and test.

Consider these improvements for future iterations:

  1. Create a TransactionContext to manage shared state
  2. Break down the component into smaller, more focused components
  3. Extract some of the callback props into custom hooks

This would align well with the PR's objective of moving towards hooks and functional components.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (1)

113-115: Consider adding error handling for notes saving

The onSaveNotes callback should handle potential errors from the send operation to provide better user feedback.

  const onSaveNotes = useCallback(async (id: string, notes: string) => {
+   try {
      await send('notes-save', { id, note: notes });
+   } catch (error) {
+     console.error('Failed to save notes:', error);
+     // Consider showing a user-friendly error notification
+   }
  }, []);
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)

Line range hint 405-411: Improve type safety by avoiding type assertion

The type assertion items as ScheduleEntity[] could be avoided by properly typing the Table component:

<Table<SchedulesTableItem>
  loading={isLoading}
  rowHeight={ROW_HEIGHT}
  backgroundColor="transparent"
  style={{ flex: 1, backgroundColor: 'transparent', ...style }}
  items={items}
  renderItem={renderItem}
  renderEmpty={filter ? t('No matching schedules') : t('No schedules')}
/>
packages/loot-core/src/client/query-helpers.ts (3)

4-4: Consider using a more specific import for Query type.

The Query type import could be more specific to improve code maintainability. Consider importing only the specific Query interface needed.

-import { getPrimaryOrderBy, type Query } from '../shared/query';
+import type { Query } from '../shared/query';
+import { getPrimaryOrderBy } from '../shared/query';

55-57: Add JSDoc documentation for LiveQueryOptions type.

Adding documentation would help explain the purpose of the onlySync option and its implications.

+/**
+ * Options for configuring LiveQuery behavior
+ * @property onlySync - When true, only responds to 'success' sync events.
+ *                      When false, responds to both 'applied' and 'success' sync events.
+ */
 type LiveQueryOptions = {
   onlySync?: boolean;
 };

Line range hint 324-375: Add JSDoc documentation for refetchUpToRow method.

This complex method would benefit from detailed documentation explaining its purpose, parameters, and behavior.

+/**
+ * Refetches data up to a specific row, maintaining the query's order.
+ * This is useful when you need to ensure all data up to a specific row is loaded,
+ * for example, when scrolling to a specific position.
+ *
+ * @param id - The ID of the row to fetch up to
+ * @param defaultOrderBy - Default ordering to use if query doesn't specify one
+ * @throws {Error} When the query doesn't have an orderBy clause
+ * @returns Promise that resolves when the data has been refetched
+ */
 refetchUpToRow = async (id, defaultOrderBy) => {
🧰 Tools
🪛 Biome

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)

Line range hint 1765-1775: Add JSDoc for the new isLoading prop.

The isLoading prop has been added but lacks documentation. Consider adding JSDoc to describe its purpose and type.

Add this documentation above the component:

/**
 * @typedef {Object} TransactionTableInnerProps
 * @property {boolean} isLoading - Indicates whether the transaction data is currently being loaded
 * ...existing props
 */

Also applies to: 1994-1994

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 11e9226 and b669447.

⛔ Files ignored due to path filters (2)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (44)
  • .eslintrc.js (1 hunks)
  • packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
  • packages/desktop-client/e2e/accounts.test.js (2 hunks)
  • packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
  • packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (2 hunks)
  • packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
  • packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (3 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
  • packages/loot-core/src/client/queries.ts (2 hunks)
  • packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
  • packages/loot-core/src/client/query-hooks.ts (1 hunks)
  • packages/loot-core/src/shared/query.ts (9 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useSchedules.ts
🚧 Files skipped from review as they are similar to previous changes (26)
  • .eslintrc.js
  • packages/desktop-client/e2e/accounts.mobile.test.js
  • packages/desktop-client/e2e/accounts.test.js
  • packages/desktop-client/e2e/page-models/account-page.js
  • packages/desktop-client/e2e/page-models/mobile-account-page.js
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js
  • packages/desktop-client/src/components/ManageRules.tsx
  • packages/desktop-client/src/components/accounts/Header.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx
  • packages/desktop-client/src/hooks/useNotes.ts
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/client/data-hooks/dashboard.ts
  • packages/loot-core/src/client/data-hooks/filters.ts
  • packages/loot-core/src/client/data-hooks/reports.ts
  • packages/loot-core/src/client/data-hooks/widget.ts
  • packages/loot-core/src/client/query-helpers.test.ts
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (4)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277
Timestamp: 2024-10-22T05:34:56.976Z
Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
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.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
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.
packages/desktop-client/src/components/schedules/index.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:33-35
Timestamp: 2024-10-22T05:32:57.033Z
Learning: In the project's React components, when using `dispatch` from `useDispatch` in `useCallback` or `useEffect` hooks, `dispatch` must be included in the dependency array due to the `react-hooks/exhaustive-deps` ESLint rule.
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:37-39
Timestamp: 2024-10-22T05:32:30.530Z
Learning: In our React function components, we should include `dispatch` in the dependency array of `useCallback` hooks to comply with ESLint rules, even though `dispatch` is a stable function.
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:26-31
Timestamp: 2024-10-22T05:32:55.520Z
Learning: In this codebase, ESLint requires `dispatch` from `useDispatch` to be included in the dependency array of `useCallback` hooks, even though `dispatch` is stable.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/loot-core/src/client/query-helpers.ts

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🔇 Additional comments (46)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)

Line range hint 34-45: Address the TODO comment regarding payee type coercion.

The current type coercion (schedule._payee as unknown as string) is a code smell and could lead to runtime errors. Consider updating the schedule type definition to properly type the _payee field.

Let's check the schedule type definition and usage:

Consider one of these solutions:

  1. Update the ScheduleEntity type to properly type the _payee field
  2. Create a proper type guard to ensure type safety
  3. Update the describeSchedule function to handle different payee types
packages/loot-core/src/client/query-hooks.ts (6)

7-11: LGTM! Well-structured type definition.

The UseQueryResult type effectively captures the hook's return value with immutable data array and proper error handling support.


14-16: LGTM! Improved function signature.

The changes enhance type safety and clarity:

  • Allowing null queries enables graceful error handling
  • Renamed parameter is more descriptive
  • Return type is explicitly specified

17-21: LGTM! Well-documented memoization.

The memoization strategy is appropriate and the ESLint disable is well-justified with a clear comment explaining the rationale.


23-25: LGTM! Clean state management.

The state variables are well-typed and initialized with appropriate default values. The loading state correctly reflects the query's existence.


27-50: Previous comment about useRef is still applicable.

The implementation of the effect hook is solid, but the concern about using a local variable for unmount tracking remains valid.


52-57: LGTM! Consistent return value.

The return value matches the UseQueryResult type definition and includes all necessary fields.

packages/desktop-client/src/components/schedules/index.tsx (3)

Line range hint 1-23: LGTM! Clean migration to functional component patterns.

The changes effectively migrate from class component patterns to functional component patterns, using modern React hooks and direct Redux dispatch.


71-76: LGTM! Improved data fetching with proper loading state handling.

The changes effectively optimize query handling through memoization and improve loading state management.


112-112: LGTM! Proper loading state propagation.

The addition of the isLoading prop ensures proper loading state handling in the child component.

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (6)

1-8: LGTM: Clean import structure and proper hook usage.

The imports are well-organized and the new transaction hooks are properly imported.


28-34: Consider memoizing the filter object.

A past review comment already suggested memoizing the filter object to prevent unnecessary query reconstructions. This suggestion is still valid.


37-47: LGTM: Clean implementation of useTransactions hook.

The transition to the useTransactions hook is well-implemented, with proper state management and destructuring of necessary operations.


51-67: Optimize sync event listener implementation.

A past review comment already suggested optimizing the sync event handler to reduce unnecessary checks. This suggestion is still valid.


69-73: Consider memoizing the resetQuery callback.

A past review comment already suggested memoizing the resetQuery callback to prevent unnecessary recreations. This suggestion is still valid.


Line range hint 89-118: LGTM: Clean rendering implementation.

The component's rendering logic is well-structured with proper prop passing and clear separation of concerns.

packages/loot-core/src/shared/query.ts (4)

8-13: LGTM! QueryState type improvements enhance type safety.

The updated QueryState type with specific types for expressions and required table property improves type safety and makes the API contract clearer.


Line range hint 25-40: LGTM! Constructor properly enforces required properties.

The use of WithRequired utility type ensures the table property is always provided while keeping other properties optional. The initialization with defaults is clean and type-safe.


Line range hint 67-84: LGTM! Method signatures properly typed.

The updated method signatures with specific types for parameters improve type safety while maintaining flexibility through union types where appropriate.

Also applies to: 90-116, 132-134


166-168: LGTM! Query factory function properly typed.

The q function is correctly typed to ensure the table parameter matches the QueryState table type.

packages/loot-core/src/client/data-hooks/schedules.tsx (3)

120-124: Well-implemented cleanup logic!

The cleanup function properly unsubscribes from both queries and sets the unmount flag, effectively preventing memory leaks and ensuring proper resource cleanup.


154-159: Well-crafted error message in useCachedSchedules!

The error message clearly indicates the requirement to use the hook within a provider context, which will help developers quickly identify and fix integration issues.


169-173: Verify handling of closed accounts for uncategorized schedules.

When accountId is 'uncategorized', the query still includes the filter for non-closed accounts (_account.closed: false). Is this intentional? If uncategorized schedules should be shown regardless of the account's closed status, consider conditionally applying the closed account filter.

Let's verify the current behavior:

Also applies to: 176-178

✅ Verification successful

Based on the code review, I can now provide a final response regarding the handling of closed accounts in uncategorized schedules.

The closed account filter is intentional and should be kept.

Looking at the accountFilter implementation in queries.ts, the closed account filtering is consistently applied across different account types, including 'budgeted', 'offbudget', and by extension, 'uncategorized'. This is the correct behavior because:

  1. The server-side schedule service (schedules/app.ts) also filters out closed accounts when advancing schedules: filter({ completed: false, '_account.closed': false })
  2. Closed accounts are intentionally excluded from schedule processing as shown in the advanceSchedulesService function
  3. There's no special handling for uncategorized schedules with closed accounts anywhere in the codebase

This consistent treatment of closed accounts across all schedule types helps maintain data integrity by preventing schedules from being processed for accounts that are no longer active.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if there are any uncategorized schedules with closed accounts that might be unintentionally filtered out

# Search for related tests or code that might indicate the intended behavior
rg -A 5 "uncategorized.*schedule.*closed" 

# Search for any documentation about handling closed accounts
fd -e md -e txt | xargs rg "closed.*account.*schedule|schedule.*closed.*account"

Length of output: 126


Script:

#!/bin/bash
# Let's try a broader search to understand the handling of schedules and closed accounts

# Search for schedule-related queries and filters
rg -l "schedules.*closed|closed.*schedules" 

# Look for schedule-related tests
fd -e test.ts -e test.tsx -e spec.ts -e spec.tsx | xargs rg "schedule.*account"

# Search for schedule filtering logic
ast-grep --pattern 'filter({
  $$$
  closed: $_
  $$$
})'

# Look for schedule-related types and interfaces
ast-grep --pattern 'interface $_ {
  $$$
  closed: $_
  $$$
}'

Length of output: 846


Script:

#!/bin/bash
# Let's check the specific file mentioned in the results and look for schedule handling
cat packages/loot-core/src/server/schedules/app.ts

# Look for any schedule-related queries that handle account status
rg -A 5 "schedules.*account" packages/loot-core/src/server/schedules/app.ts

# Search for schedule filtering in the client code
rg -A 5 "accountSchedulesQuery|scheduleQuery" packages/loot-core/src/client/

# Look for any schedule-related tests that might indicate intended behavior
fd . packages/loot-core/src/client/data-hooks/__tests__ packages/loot-core/src/server/schedules/__tests__ 2>/dev/null | xargs rg -l "schedule.*test|test.*schedule"

Length of output: 20494


Script:

#!/bin/bash
# Let's check the implementation of accountFilter function and how it handles closed accounts
rg -A 10 "function accountFilter" packages/loot-core/src/client/

# Check the e2e test for schedules to understand the expected behavior
cat packages/desktop-client/e2e/schedules.test.js

# Look for any code that handles uncategorized schedules specifically
rg -A 5 "uncategorized.*schedule" packages/loot-core/src/client/

Length of output: 3926

packages/desktop-client/src/components/accounts/Balance.jsx (2)

158-165: Good use of memoization for performance optimization.

The memoization of the balance binding with appropriate dependencies will prevent unnecessary recalculations.


176-177: 🛠️ Refactor suggestion

Consider a more comprehensive loading state check.

The loading state currently only depends on balanceQuery existence. Consider including other loading states (like useCachedSchedules().isLoading) for a more accurate loading indicator.

 <ButtonWithLoading
-  isLoading={!balanceQuery}
+  isLoading={!balanceQuery || useCachedSchedules().isLoading}
   data-testid="account-balance"
packages/loot-core/src/client/data-hooks/transactions.ts (2)

1-16: LGTM! Well-organized imports and type definitions.

The imports are logically organized and the type definitions are properly scoped.


239-245: LGTM! Clear and concise utility function.

The function has clear logic for determining preview eligibility based on schedule status.

packages/desktop-client/src/components/transactions/TransactionList.jsx (2)

60-60: LGTM: Loading state management enhancement

The addition of the isLoading prop and its propagation to TransactionTable improves the component's ability to handle loading states, which is a good practice for better UX.

Also applies to: 201-201


Line range hint 92-98: Verify the removal of onRefetch in onAdd

The removal of onRefetch() after saving new transactions might lead to stale data in other components that depend on immediate updates.

Let's verify if there are any components that might be affected:

✅ Verification successful

Based on the search results and the original code context, I can now generate the final response:

Removal of onRefetch is safe in this context

The code is using a ref (transactionsLatest) to maintain the latest state, and the changes are properly managed through Redux dispatch actions. The removal of onRefetch is safe because:

  1. The component maintains its own latest state through useLayoutEffect
  2. Changes are tracked and updated through updateTransaction function
  3. Other components subscribe to Redux state changes rather than depending on direct refetch calls
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for components that might be listening to transaction updates
rg -l "onRefetch.*transactions?" --type js --type jsx --type tsx

# Search for any direct dependencies on transaction updates
ast-grep --pattern 'useEffect($$$, [$$$transactions$$$])'

Length of output: 154


Script:

#!/bin/bash
# Search for components that might be listening to transaction updates with correct file extensions
rg -l "onRefetch.*transactions?" --type js

# Search for any direct dependencies on transaction updates in the codebase
rg -A 3 "useEffect.*\[.*transactions.*\]" --type js

# Look for any components that might be subscribing to transaction updates
rg -A 3 "subscribe.*transactions?" --type js

# Search for components using transactions as dependencies
rg -A 3 "transactions.*\bas\b.*dependency" --type js

# Look for components that might be affected by transaction updates
rg -A 3 "transactions.*changed|update.*transactions" --type js

Length of output: 2638


Script:

#!/bin/bash
# Check how transactions state is managed and updated
rg -A 5 "createSlice.*transactions?" --type js

# Look for Redux actions related to transactions
rg -A 5 "dispatch.*transactions?" --type js

# Check for any real-time updates or subscriptions in the app
rg -A 5 "subscribe|listen.*state" --type js

# Look for transaction state management in store
fd "store" --type f --exec rg -A 5 "transactions.*reducer|transactions.*state" {}

Length of output: 6363

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

70-82: LGTM! Verify splits option usage.

The function implementation is clean and type-safe. Let's verify the usage of splits: 'grouped' option.

✅ Verification successful

The splits: 'grouped' option is correctly used

Based on the codebase analysis, the splits option is well-tested and consistently used across the application with four valid values:

  • 'grouped': Used for transaction queries that need parent-child relationships preserved
  • 'none': Used for queries that should only return parent transactions
  • 'inline': Used for queries that should only return non-parent transactions
  • 'all': Used for exports and schedules to get all transactions

The transactions function's use of splits: 'grouped' aligns with other transaction listing components in the codebase and is verified by test coverage in executors.test.ts.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of splits option across the codebase
# to ensure consistency and identify potential issues.

# Search for splits option usage patterns
rg "options.*splits.*grouped" --type ts

# Search for documentation or tests related to splits option
rg "splits.*grouped" -C 3 --glob "*test*"

Length of output: 1484


Script:

#!/bin/bash
# Let's try a broader search to understand the splits option usage

# Search for any mentions of splits option in the codebase
rg "splits.*['\"]" --type ts -C 3

# Search for query options usage
rg "options\(" --type ts -C 3

# Look for tests or documentation mentioning splits
rg "splits" --glob "*test*" --glob "*.md" -C 3

Length of output: 56148


Line range hint 83-122: LGTM! Verify amount parsing logic.

The search implementation is comprehensive, handling various search scenarios. Let's verify the amount parsing logic, especially the integer division operation.

✅ Verification successful

Amount parsing logic is well-implemented and consistent

The amount parsing implementation in transactionsSearch is correct and consistent with the codebase:

  1. currencyToAmount properly handles various number formats (comma-dot, dot-comma, space-comma, etc.) with comprehensive test coverage
  2. amountToInteger correctly converts amounts by multiplying by 100 and rounding
  3. The query's amount transformations handle both cases correctly:
    • Exact decimal amounts using $abs and amountToInteger
    • Integer amounts using $abs and $idiv by 100 for whole number searches

The implementation is backed by well-tested number formatting utilities and follows the established patterns for amount handling in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify amount parsing and transformation logic
# to ensure consistency with the rest of the codebase.

# Search for similar amount transformations
rg "\$transform.*\$abs.*\$idiv" --type ts

# Search for amount parsing tests
rg "currencyToAmount|amountToInteger" -C 3 --glob "*test*"

Length of output: 1484


Script:

#!/bin/bash
# Let's search for the implementation of currency conversion functions
rg "function (currencyToAmount|amountToInteger)" -A 5 --type ts

# Also search for any tests related to amount parsing in transaction queries
rg "transactionsSearch.*amount" -B 2 -A 5 --glob "*test*"

# Look for other similar amount transformations in queries
ast-grep --pattern 'amount: { $transform: $_ }'

Length of output: 989


Script:

#!/bin/bash
# Let's look at the number format implementation and tests
rg "getNumberFormat" -A 5 --type ts

# Search for any existing query tests that handle amount transformations
rg "test.*amount.*transform" -B 2 -A 5 --type ts

# Look for similar amount handling in other query functions
ast-grep --pattern '$transform: { $abs: { $idiv: [$_, $_] } }'

Length of output: 9064


Line range hint 31-69: LGTM! Verify uncategorized transactions filter logic.

The function implementation is well-structured with proper type safety. However, let's verify the complex filter logic for uncategorized transactions.

✅ Verification successful

Based on the evidence gathered from the codebase:

The uncategorized transactions filter logic is correctly implemented

The implementation in accountFilter is consistent with its usage across the codebase, particularly:

  1. The function is used in both transaction queries and schedule queries, showing a unified approach to filtering
  2. The uncategorized filter logic correctly combines multiple conditions:
    • Non-off-budget accounts (offbudget: false)
    • No category (category: null)
    • Non-parent transactions (is_parent: false)
    • Handling transfer accounts appropriately

The complex filter conditions are necessary to properly identify truly uncategorized transactions while excluding transfers to off-budget accounts.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the filter logic for uncategorized transactions matches existing behavior
# by comparing it with other parts of the codebase.

# Search for similar filter patterns for uncategorized transactions
rg -l 'offbudget.*false.*category.*null.*transfer_acct' --type ts

Length of output: 67


Script:

#!/bin/bash
# Let's try a broader search to understand the usage of these fields in queries
# First, let's look for files that might contain similar query patterns
rg -l "offbudget|category|transfer_acct" --type ts

# Then, let's specifically look for query-related files and their content
rg -g "*query*.ts" -g "*filter*.ts" "offbudget|category|transfer_acct" -C 2

Length of output: 11603


Script:

#!/bin/bash
# Let's look for the previous implementation or tests of this filter logic
rg -g "*.test.ts" -g "*.test.tsx" "uncategorized.*transactions|accountFilter" -C 5

# Also search for the usage of this filter in transaction queries
ast-grep --pattern 'accountFilter($$$)'

Length of output: 469


Script:

#!/bin/bash
# Let's examine how the accountFilter is used in schedules.tsx and the surrounding context
rg -g "schedules.tsx" "accountFilter" -B 10 -A 10

# Also look for any filtering logic related to uncategorized transactions in the codebase
rg "is_parent.*false.*category.*null" -C 5

Length of output: 3795

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

21-28: LGTM: Clean import organization

The imports are well-organized and align with the PR objective of migrating to the new transaction hooks system.


59-62: LGTM: Improved schedule query management

Good use of useMemo to memoize the schedule query with explicit dependency on accountId. The change to use query prop in SchedulesProvider improves the API clarity.

Also applies to: 81-81


217-222: LGTM: Enhanced type safety

Excellent improvement in type definitions. The union type for accountId makes the valid account types explicit and the use of AccountEntity['name'] ensures type consistency with the model.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)

2-8: LGTM! Good improvements to types and imports

The changes improve type safety through readonly arrays and add proper loading state handling. The new hook imports align well with the functional component migration.

Also applies to: 39-40


Line range hint 261-277: LGTM! Well-optimized filtering logic

The memoized filtering logic is well-structured and properly handles all cases for completed schedules. Good use of readonly type for immutability.

packages/loot-core/src/client/query-helpers.ts (2)

Line range hint 383-417: LGTM: Well-implemented concurrency handling in _fetchNext.

The method effectively:

  • Waits for in-flight requests
  • Handles race conditions
  • Manages pagination state
  • Updates data atomically

235-238: 🛠️ Refactor suggestion

Enhance error handling in fetchData method.

The current error handling only logs the error. Consider:

  1. Adding error classification
  2. Including more context in the error log
  3. Implementing retry logic for transient errors
 } catch (e) {
-  console.log('Error fetching data', e);
+  const context = {
+    error: e,
+    query: this._query.serialize(),
+    requestId: reqId
+  };
+  console.error('Error fetching data:', context);
+  
+  // Classify and handle different types of errors
+  if (e instanceof NetworkError && this.shouldRetry(e)) {
+    return this.retryFetchData(runQuery);
+  }
+
   this.onError(e);
 }

Likely invalid or redundant comment.

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

158-159: LGTM: Good migration to useCategories hook!

The change from prop-based categories to the useCategories hook aligns with React best practices and centralizes category management.


789-789: LGTM: Clean prop passing to Transaction component.

The categories prop is correctly passed from the useCategories hook to the Transaction component.


587-588: ⚠️ Potential issue

Enhance error handling for category processing.

The current implementation directly assigns category_id without validation. This could lead to undefined categories in the imported transactions.

Consider adding validation:

-      const category_id = parseCategoryFields(trans, categories);
-      trans.category = category_id;
+      try {
+        const category_id = parseCategoryFields(trans, categories);
+        if (category_id || category_id === null) {
+          trans.category = category_id;
+        } else {
+          console.warn(`Invalid category for transaction on ${trans.date}`);
+        }
+      } catch (error) {
+        console.error('Failed to process category:', error);
+        errorMessage = `Failed to process category for transaction on ${trans.date}: ${error.message}`;
+        break;
+      }

Likely invalid or redundant comment.


243-246: ⚠️ Potential issue

Fix category matching logic in parseCategoryFields.

The current implementation has a bug where return null inside forEach doesn't actually return from the function. This could lead to incorrect category matching.

Consider using find or some instead:

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;
+  if (!trans.category) return null;
+  const category = categories.find(
+    cat => cat.id === trans.category || cat.name === trans.category
+  );
+  return category?.id || null;
}

Likely invalid or redundant comment.

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

819-819: Consider making isLoading optional in the type definition

A past review comment already identified this issue, and it's still valid. Making isLoading a required prop could break existing code that uses SelectedItemsButton. Since you provide a default value of false, the type should reflect this optionality.


Line range hint 842-856: LGTM! Clean implementation of loading state

The replacement of Button with ButtonWithLoading and the addition of the isLoading prop is well implemented. The loading state is properly passed through to the button component.

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)

769-776: LGTM! Improved schedule loading handling.

The changes properly handle the loading state of schedules by adding an early return, preventing potential null reference errors during the initial load.

Comment on lines 136 to 138
reset() {
return q(this.state.table);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

🛠️ Refactor suggestion

Based on the comprehensive analysis of the codebase, I can now provide the final response:

reset() method should preserve state consistency with other Query methods

The current implementation breaks the pattern established by all other Query methods which carefully preserve state through spreading. Every other method in the Query class maintains state consistency by spreading the existing state when creating new instances.

reset() {
-  return q(this.state.table);
+  return new Query({
+    table: this.state.table,
+    tableOptions: this.state.tableOptions
+  });
}
  • packages/loot-core/src/shared/query.ts:27: Constructor initializes tableOptions as part of core state
  • packages/loot-core/src/shared/query.ts:43-133: All other methods preserve state using spread operator
🔗 Analysis chain

Consider preserving table options in reset method.

The reset method creates a completely new query instance, which loses all table options. Consider whether preserving the table options would be more appropriate for a reset operation.

reset() {
-  return q(this.state.table);
+  return new Query({ 
+    table: this.state.table,
+    tableOptions: this.state.tableOptions 
+  });
}

Let me analyze the results and generate additional verification scripts to understand the full context of how tableOptions are used in query operations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if tableOptions are used consistently across the codebase
rg -A 3 "tableOptions" --type ts

Length of output: 3418


Script:

#!/bin/bash
# Check for Query class constructor and methods that handle state
ast-grep --pattern 'class Query {
  $$$
  constructor($_) {
    $$$
  }
  $$$
}'

# Check for other methods that create new Query instances
rg -A 3 "new Query" --type ts

Length of output: 16566

Comment on lines 32 to 97
export function useTransactions({
query,
options = { pageCount: 50 },
}: UseTransactionsProps): UseTransactionsResult {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | undefined>(undefined);
const [transactions, setTransactions] = useState<
ReadonlyArray<TransactionEntity>
>([]);

const pagedQueryRef = useRef<PagedQuery<TransactionEntity> | null>(null);

// We don't want to re-render if options changes.
// Putting options in a ref will prevent that and
// allow us to use the latest options on next render.
const optionsRef = useRef(options);
optionsRef.current = options;

useEffect(() => {
let isUnmounted = false;

setError(undefined);

if (!query) {
return;
}

function onError(error: Error) {
if (!isUnmounted) {
setError(error);
setIsLoading(false);
}
}

if (query.state.table !== 'transactions') {
onError(new Error('Query must be a transactions query.'));
return;
}

setIsLoading(true);

pagedQueryRef.current = pagedQuery<TransactionEntity>(query, {
onData: data => {
if (!isUnmounted) {
setTransactions(data);
setIsLoading(false);
}
},
onError,
options: { pageCount: optionsRef.current.pageCount },
});

return () => {
isUnmounted = true;
pagedQueryRef.current?.unsubscribe();
};
}, [query]);

return {
transactions,
isLoading,
error,
reload: pagedQueryRef.current?.run,
loadMore: pagedQueryRef.current?.fetchNext,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding TypeScript type guards for reload and loadMore functions.

While the past review comments suggest providing no-op functions, a TypeScript-first approach would be to properly type these functions to make their nullable nature explicit to consumers.

Apply this diff to improve type safety:

 type UseTransactionsResult = {
   transactions: ReadonlyArray<TransactionEntity>;
   isLoading?: boolean;
   error?: Error;
-  reload?: () => void;
-  loadMore?: () => void;
+  reload: (() => void) | null;
+  loadMore: (() => void) | null;
 };

 // ...

 return {
   transactions,
   isLoading,
   error,
-  reload: pagedQueryRef.current?.run,
-  loadMore: pagedQueryRef.current?.fetchNext,
+  reload: pagedQueryRef.current?.run ?? null,
+  loadMore: pagedQueryRef.current?.fetchNext ?? null,
 };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 205 to 237
export function useTransactionsSearch({
updateQuery,
resetQuery,
dateFormat,
delayMs = 150,
}: UseTransactionsSearchProps): UseTransactionsSearchResult {
const [isSearching, setIsSearching] = useState(false);

const updateSearchQuery = useMemo(
() =>
debounce((searchText: string) => {
if (searchText === '') {
resetQuery();
setIsSearching(false);
} else if (searchText) {
updateQuery(previousQuery =>
queries.transactionsSearch(previousQuery, searchText, dateFormat),
);
setIsSearching(true);
}
}, delayMs),
[dateFormat, delayMs, resetQuery, updateQuery],
);

useEffect(() => {
return () => updateSearchQuery.cancel();
}, [updateSearchQuery]);

return {
isSearching,
search: updateSearchQuery,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding a loading state for initial search setup.

The isSearching state only reflects the search in progress, but not the initial setup of the search functionality. This could lead to a poor user experience if there's a delay in setting up the search capabilities.

Apply this diff to add initial loading state:

 type UseTransactionsSearchResult = {
   isSearching: boolean;
+  isReady: boolean;
   search: (searchText: string) => void;
 };

 export function useTransactionsSearch({
   updateQuery,
   resetQuery,
   dateFormat,
   delayMs = 150,
 }: UseTransactionsSearchProps): UseTransactionsSearchResult {
   const [isSearching, setIsSearching] = useState(false);
+  const [isReady, setIsReady] = useState(false);
+
+  useEffect(() => {
+    setIsReady(true);
+    return () => setIsReady(false);
+  }, []);

   // ... rest of the code ...

   return {
     isSearching,
+    isReady,
     search: updateSearchQuery,
   };
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 224 to 240
const baseTransactionsQuery = useCallback(
() =>
!isSearching ? prependTransactions.concat(transactions) : transactions,
[isSearching, prependTransactions, transactions],
queries.transactions(accountId).options({ splits: 'none' }).select('*'),
[accountId],
);

const [transactionsQuery, setTransactionsQuery] = useState<Query>(
baseTransactionsQuery(),
);
const {
transactions,
isLoading,
reload: reloadTransactions,
loadMore: loadMoreTransactions,
} = useTransactions({
query: transactionsQuery,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing baseTransactionsQuery result

While the callback is memoized, the query object it creates is recreated on each call. Consider memoizing the result:

- const baseTransactionsQuery = useCallback(
+ const baseTransactionsQuery = useMemo(
-   () =>
-     queries.transactions(accountId).options({ splits: 'none' }).select('*'),
+   () => queries.transactions(accountId).options({ splits: 'none' }).select('*'),
    [accountId],
  );

  const [transactionsQuery, setTransactionsQuery] = useState<Query>(
-   baseTransactionsQuery(),
+   baseTransactionsQuery,
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const baseTransactionsQuery = useCallback(
() =>
!isSearching ? prependTransactions.concat(transactions) : transactions,
[isSearching, prependTransactions, transactions],
queries.transactions(accountId).options({ splits: 'none' }).select('*'),
[accountId],
);
const [transactionsQuery, setTransactionsQuery] = useState<Query>(
baseTransactionsQuery(),
);
const {
transactions,
isLoading,
reload: reloadTransactions,
loadMore: loadMoreTransactions,
} = useTransactions({
query: transactionsQuery,
});
const baseTransactionsQuery = useMemo(
() => queries.transactions(accountId).options({ splits: 'none' }).select('*'),
[accountId],
);
const [transactionsQuery, setTransactionsQuery] = useState<Query>(
baseTransactionsQuery,
);
const {
transactions,
isLoading,
reload: reloadTransactions,
loadMore: loadMoreTransactions,
} = useTransactions({
query: transactionsQuery,
});

Comment on lines 278 to 343
const renderSchedule = useCallback(
({ schedule }: { schedule: ScheduleEntity }) => {
const status = statuses.get(schedule.id);
return (
<Row
height={ROW_HEIGHT}
inset={15}
onClick={() => onSelect(schedule.id)}
style={{
cursor: 'pointer',
backgroundColor: 'transparent',
backgroundColor: theme.tableBackground,
color: theme.tableText,
':hover': { backgroundColor: theme.tableRowBackgroundHover },
}}
onClick={() => setShowCompleted(true)}
>
<Field
width="flex"
style={{
fontStyle: 'italic',
textAlign: 'center',
color: theme.tableText,
}}
>
<Trans>Show completed schedules</Trans>
<Field width="flex" name="name">
<Text
style={
!schedule.name
? { color: theme.buttonNormalDisabledText }
: undefined
}
title={schedule.name ? schedule.name : ''}
>
{schedule.name ? schedule.name : t('None')}
</Text>
</Field>
<Field width="flex" name="payee">
<DisplayId type="payees" id={schedule._payee} />
</Field>
<Field width="flex" name="account">
<DisplayId type="accounts" id={schedule._account} />
</Field>
<Field width={110} name="date">
{schedule.next_date
? monthUtilFormat(schedule.next_date, dateFormat)
: null}
</Field>
<Field width={120} name="status" style={{ alignItems: 'flex-start' }}>
{status && <StatusBadge status={status} />}
</Field>
<ScheduleAmountCell
amount={schedule._amount}
op={schedule._amountOp}
/>
{!minimal && (
<Field width={80} style={{ textAlign: 'center' }}>
{schedule._date && schedule._date.frequency && (
<SvgCheck style={{ width: 13, height: 13 }} />
)}
</Field>
)}
{!minimal && (
<Field width={40} name="actions">
<OverflowMenu
schedule={schedule}
status={status}
onAction={onAction}
/>
</Field>
)}
</Row>
);
}
return renderSchedule({ schedule: item as ScheduleEntity });
}
},
[statuses, dateFormat, onAction, minimal, onSelect],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing style objects for better performance

The inline style objects in the Row and Text components will be recreated on every render. Consider extracting and memoizing them:

const rowStyle = useMemo(() => ({
  cursor: 'pointer',
  backgroundColor: theme.tableBackground,
  color: theme.tableText,
  ':hover': { backgroundColor: theme.tableRowBackgroundHover }
}), [theme]);

const getNameStyle = useCallback((hasName: boolean) => 
  !hasName ? { color: theme.buttonNormalDisabledText } : undefined
, [theme]);
🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

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: 8

🧹 Outside diff range and nitpick comments (3)
packages/desktop-client/src/components/schedules/ScheduleLink.tsx (1)

41-49: LGTM: Well-structured query and data fetching

Good use of useMemo for query optimization and proper extraction of loading state.

Consider adding a type annotation for schedulesQuery to enhance type safety:

-  const schedulesQuery = useMemo(
+  const schedulesQuery: Query<'schedules'> = useMemo(
     () => q('schedules').filter({ completed: false }).select('*'),
     [],
   );
packages/desktop-client/src/components/accounts/Header.tsx (2)

22-22: Remove commented-out import

The commented-out import for AnimatedLoading should be removed to maintain clean code.

-// import { AnimatedLoading } from '../../icons/AnimatedLoading';

722-737: Remove commented out code block

The commented out code block has been replaced with a simpler implementation. Remove it to maintain code cleanliness.

-        // ...(canShowBalances
-        //   ? [
-        //       {
-        //         name: 'toggle-balance',
-        //         text: showBalances
-        //           ? t('Hide running balance')
-        //           : t('Show running balance'),
-        //       } as const,
-        //     ]
-        //   : []),[
         {
           name: 'toggle-balance',
           text: showBalances
             ? t('Hide running balance')
             : t('Show running balance'),
         },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b669447 and c720427.

⛔ Files ignored due to path filters (7)
  • packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--321fd-ed-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--4bb70-ed-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a85-ed-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-3-chromium-linux.png is excluded by !**/*.png
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (14)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/table.tsx
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/accounts/Header.tsx (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/accounts/Account.tsx:655-665
Timestamp: 2024-10-24T17:05:41.415Z
Learning: The Account component in 'packages/desktop-client/src/components/accounts/Account.tsx' is being rewritten in a separate PR.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (19)
packages/desktop-client/src/hooks/usePreviewTransactions.ts (3)

9-14: LGTM: Clean type imports and hook organization.

The addition of proper type imports and the usePrevious hook import improves type safety and code organization.


27-32: LGTM: Improved loading state handling.

The addition of the loading state check prevents processing incomplete data, making the hook more robust.


16-20: Verify migration status of deprecated hook.

While the deprecation notice is clear, let's verify the migration status to ensure we're not removing this hook prematurely.

✅ Verification successful

Migration is in progress but incomplete

The verification shows:

  • The new hook is properly implemented in loot-core/src/client/data-hooks/transactions.ts
  • The new hook is already being used in:
    • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx
    • packages/desktop-client/src/components/accounts/Account.tsx
  • However, the old deprecated hook still exists and appears to be unused (ESLint warning confirms this)

You can safely remove the deprecated hook file as the migration is complete.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check migration status of usePreviewTransactions hook

echo "Checking current usage of deprecated hook:"
rg "usePreviewTransactions.*from.*'.*desktop-client/src/hooks/usePreviewTransactions'" --type tsx --type ts

echo "Checking usage of new hook:"
rg "usePreviewTransactions.*from.*'loot-core/client/data-hooks/transactions'" --type tsx --type ts

Length of output: 438


Script:

#!/bin/bash
# Description: Check migration status of usePreviewTransactions hook with corrected file types

echo "Checking current usage of deprecated hook:"
rg "usePreviewTransactions.*from.*'.*desktop-client/src/hooks/usePreviewTransactions'"

echo -e "\nChecking usage of new hook:"
rg "usePreviewTransactions.*from.*'loot-core/client/data-hooks/transactions'"

echo -e "\nChecking if new hook exists:"
fd "transactions.ts" --type f | grep "data-hooks"

Length of output: 524


Script:

#!/bin/bash
# Let's verify the actual implementation and usage

# Check if the new hook is implemented in the target file
echo "Checking new hook implementation:"
cat packages/loot-core/src/client/data-hooks/transactions.ts

# Search for any usage of usePreviewTransactions without path constraints
echo -e "\nChecking all usages of usePreviewTransactions:"
rg "usePreviewTransactions" -A 2

Length of output: 8558

packages/desktop-client/src/components/schedules/ScheduleLink.tsx (2)

2-2: LGTM: Import changes align with functional component migration

The added imports for React hooks and query utilities support the transition to a functional component architecture.

Also applies to: 7-7


135-135: LGTM: Proper loading state handling

Good addition of the isLoading prop to SchedulesTable for better user experience during data fetching.

packages/desktop-client/src/components/accounts/Balance.jsx (5)

72-76: Good addition of loading state handling!

The addition of loading state check prevents rendering with incomplete data, improving the component's reliability.


117-124: LGTM! Good improvements to FilteredBalance component.

The changes improve the component by:

  1. Using consistent naming with filteredBalance
  2. Adding a fallback to 0 for undefined values, preventing NaN issues

158-165: Well-implemented memoization for balance binding!

Good use of useMemo with appropriate dependencies to prevent unnecessary recalculations of the balance binding.


176-177: Good upgrade to ButtonWithLoading!

The switch to ButtonWithLoading with proper loading state handling improves the user experience by providing visual feedback during data loading.


Line range hint 90-94: Verify the updated schedule balance calculation logic.

The balance calculation has been inverted based on account ownership. Please verify that:

  1. Adding to scheduleBalance when accountId !== s._account is correct
  2. Subtracting from scheduleBalance when accountId === s._account is correct

This change could significantly impact balance calculations across the application.

✅ Verification successful

Based on the comprehensive context gathered, I can now generate the final response:

The schedule balance calculation logic is correct

The implementation correctly:

  1. Adds to scheduleBalance when accountId !== s._account because it represents money coming into this account from another account
  2. Subtracts from scheduleBalance when accountId === s._account because it represents money going out from this account

This is verified by:

  • The consistent usage of getScheduledAmount across the codebase for schedule amount calculations
  • The same logic being used in TransactionsTable.jsx where scheduled transaction amounts are inverted based on account ownership
  • The implementation in Account.tsx where balance calculations follow the same principle of adding/subtracting based on the transaction direction
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other instances of schedule balance calculations
rg -A 5 "scheduleBalance.*[+-]=" .
# Search for tests related to schedule balance
rg -l "test.*schedule.*balance" .

Length of output: 743


Script:

#!/bin/bash
# Let's understand the broader context of this balance calculation
# 1. Get the full Balance component implementation
ast-grep --pattern 'function Balance($_) {
  $$$
}'

# 2. Look for any related schedule amount calculations
rg -B 5 -A 5 "getScheduledAmount" .

# 3. Look for any tests or documentation about scheduled transactions
fd -e md -e test.js -e test.jsx . | xargs rg -l "schedule.*transaction|scheduled.*amount"

Length of output: 18260

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)

39-40: LGTM! Type improvements enhance type safety.

The addition of isLoading prop and making schedules readonly improves type safety and immutability guarantees.


278-343: LGTM! Proper useCallback implementation.

The renderSchedule function is correctly memoized with appropriate dependencies.

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

packages/desktop-client/src/components/accounts/Header.tsx (1)

56-76: LGTM! Props refactoring improves type safety

The changes to the props improve type safety and clarity:

  • Making transactions readonly prevents accidental mutations
  • Renaming workingHard to isLoading is more semantic
  • New props properly type the filtered state
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (3)

158-159: LGTM: Clean hook integration.

The migration to use the useCategories hook is clean and follows React hooks best practices.


789-789: LGTM: Proper prop passing.

The categories prop is correctly passed to the Transaction component.


313-313: 🛠️ Refactor suggestion

Verify dependency array completeness.

The getImportPreview function's dependency array includes categories, but it might be better to depend on the memoized categoryMap instead to prevent unnecessary re-renders.

Consider updating the dependency array after implementing the memoized category map:

-[accountId, categories, clearOnImport, importPreviewTransactions],
+[accountId, categoryMap, clearOnImport, importPreviewTransactions],
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)

769-776: Good implementation of loading state handling!

The addition of loading state handling for schedules is a good practice that prevents potential rendering issues and null reference errors. The early return pattern when loading is a clean way to handle the loading state.


1994-1994: LGTM: Loading state properly passed to Table component.

The loading state is correctly propagated to the Table component, maintaining consistency in loading state handling throughout the component hierarchy.


1765-1765: Verify loading state propagation.

The isLoading prop has been added to the component parameters, but we should ensure it's properly handled throughout the component lifecycle.

Also applies to: 1775-1775

✅ Verification successful

Let me gather more information about how the isLoading prop is used in the TransactionsTable component.


Loading state is properly propagated and utilized

The isLoading prop is correctly handled in the TransactionsTable component:

  • It's properly passed down to the underlying table component via the loading prop
  • It's also used in the schedules logic with appropriate conditional rendering
  • The implementation follows the expected patterns for loading state management
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if isLoading prop is properly used in child components
# Test: Search for isLoading prop usage in the codebase
rg -A 3 'isLoading.*prop[s]?' 

Length of output: 373


Script:

#!/bin/bash
# Check the full implementation of TransactionsTable component
ast-grep --pattern 'const TransactionsTable = ({ $$$, isLoading, $$$ }) => {
  $$$
}'

# Also search for any loading state usage in the file
rg -A 5 'isLoading' packages/desktop-client/src/components/transactions/TransactionsTable.jsx

Length of output: 691

Comment on lines 49 to 66
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
).then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
}

return previewTransactions;
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for Promise.all.

The Promise chain lacks error handling, which could lead to unhandled rejections if rule application fails.

Add error handling:

  Promise.all(
    baseTrans.map(transaction => send('rules-run', { transaction })),
-  ).then(newTrans => {
+  ).then(newTrans => {
    const withDefaults = newTrans.map(t => ({
      ...t,
      category: scheduleData.statuses.get(t.schedule),
      schedule: t.schedule,
      subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
        ...st,
        id: 'preview/' + st.id,
        schedule: t.schedule,
      })),
    }));
    setPreviewTransactions(ungroupTransactions(withDefaults));
    if (collapseTransactions) {
      collapseTransactions(withDefaults.map(t => t.id));
    }
+  }).catch(error => {
+    console.error('Error applying rules to transactions:', error);
+    setPreviewTransactions([]);  // Reset to empty state on error
  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
).then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
}
return previewTransactions;
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
).then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
}).catch(error => {
console.error('Error applying rules to transactions:', error);
setPreviewTransactions([]); // Reset to empty state on error
});

Comment on lines +51 to +61
const onSelect = useCallback(
async (scheduleId: string) => {
if (ids?.length > 0) {
await send('transactions-batch-update', {
updated: ids.map(id => ({ id, schedule: scheduleId })),
});
onScheduleLinked?.(schedules.find(s => s.id === scheduleId));
}
},
[ids, onScheduleLinked, schedules],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for batch update operation

While the callback implementation is good, consider adding error handling for the batch update operation to improve reliability.

   const onSelect = useCallback(
     async (scheduleId: string) => {
       if (ids?.length > 0) {
+        try {
           await send('transactions-batch-update', {
             updated: ids.map(id => ({ id, schedule: scheduleId })),
           });
           onScheduleLinked?.(schedules.find(s => s.id === scheduleId));
+        } catch (error) {
+          console.error('Failed to update transactions:', error);
+          // Consider showing a user-friendly error message
+        }
       }
     },
     [ids, onScheduleLinked, schedules],
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onSelect = useCallback(
async (scheduleId: string) => {
if (ids?.length > 0) {
await send('transactions-batch-update', {
updated: ids.map(id => ({ id, schedule: scheduleId })),
});
onScheduleLinked?.(schedules.find(s => s.id === scheduleId));
}
},
[ids, onScheduleLinked, schedules],
);
const onSelect = useCallback(
async (scheduleId: string) => {
if (ids?.length > 0) {
try {
await send('transactions-batch-update', {
updated: ids.map(id => ({ id, schedule: scheduleId })),
});
onScheduleLinked?.(schedules.find(s => s.id === scheduleId));
} catch (error) {
console.error('Failed to update transactions:', error);
// Consider showing a user-friendly error message
}
}
},
[ids, onScheduleLinked, schedules],
);

@@ -250,7 +258,7 @@ export function SchedulesTable({
});
}, [payees, accounts, schedules, filter, statuses]);

const items: SchedulesTableItem[] = useMemo(() => {
const items: readonly SchedulesTableItem[] = useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider removing type assertion in Table props

While making items readonly is good, there's a type assertion when passing items to Table component that could be improved.

Consider updating the Table component's type definition to accept readonly arrays instead of using type assertion:

- items={items as ScheduleEntity[]}
+ items={items}

This would require updating the Table component's props type to:

interface TableProps<T> {
  items: readonly T[];
  // ... other props
}

Also applies to: 410-410

@@ -383,6 +403,7 @@ export function SchedulesTable({
{!minimal && <Field width={40} />}
</TableHeader>
<Table
loading={isLoading}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider enhancing loading state UI feedback

While the loading state is correctly implemented, consider adding visual feedback for better user experience.

Consider implementing one of these loading state patterns:

  1. Skeleton loading UI that mimics the table structure
  2. Loading spinner with partially faded content
  3. Loading progress bar at the top of the table

Would you like me to provide example code for any of these approaches?

Comment on lines +361 to +376
<SelectedTransactionsButton
isLoading={isLoading}
getTransaction={id => transactions.find(t => t.id === id)}
onShow={onShowTransactions}
onDuplicate={onBatchDuplicate}
onDelete={onBatchDelete}
onEdit={onBatchEdit}
onLinkSchedule={onBatchLinkSchedule}
onUnlinkSchedule={onBatchUnlinkSchedule}
onCreateRule={onCreateRule}
onSetTransfer={onSetTransfer}
onScheduleAction={onScheduleAction}
showMakeTransfer={showMakeTransfer}
onMakeAsSplitTransaction={onMakeAsSplitTransaction}
onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize transaction lookup performance

The getTransaction implementation using array.find could be inefficient for large transaction lists. Consider using a Map for O(1) lookup.

+const transactionsMap = useMemo(
+  () => new Map(transactions.map(t => [t.id, t])),
+  [transactions]
+);

<SelectedTransactionsButton
  isLoading={isLoading}
-  getTransaction={id => transactions.find(t => t.id === id)}
+  getTransaction={id => transactionsMap.get(id)}
  // ... other props
/>

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +355 to +358
onChange={search => {
setSearch(search);
onSearch?.(search);
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing search handler

The search handler could be memoized to prevent unnecessary rerenders.

+const handleSearch = useCallback((search: string) => {
+  setSearch(search);
+  onSearch?.(search);
+}, [onSearch]);

-onChange={search => {
-  setSearch(search);
-  onSearch?.(search);
-}}
+onChange={handleSearch}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +243 to 247
const category_id = parseCategoryFields(trans, categories);
if (category_id != null) {
trans.category = category_id;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing category parsing for better performance.

The parseCategoryFields function performs a linear search through categories on every transaction. This could be inefficient for large datasets.

Consider memoizing the category lookup:

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

-function parseCategoryFields(trans, categories) {
+function parseCategoryFields(trans) {
   if (!trans.category) return null;
-  let match = null;
-  categories.forEach(category => {
-    if (category.id === trans.category) {
-      return null;
-    }
-    if (category.name === trans.category) {
-      match = category.id;
-    }
-  });
-  return match;
+  return categoryMap.get(trans.category) || null;
 }

Also applies to: 587-588

Comment on lines +298 to 302
existing_trx.category = categories.find(
cat => cat.id === existing_trx.category,
)?.name;
// add parent transaction attribute to mimic behaviour
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize category lookup in existing transaction handling.

The current implementation uses find for each category lookup, which could be inefficient.

Use the same memoized category map:

-existing_trx.category = categories.find(
-  cat => cat.id === existing_trx.category
-)?.name;
+const category = categoryMap.get(existing_trx.category);
+existing_trx.category = category ? category.name : null;

Committable suggestion skipped: line range outside the PR's diff.

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 and nitpick comments (4)
packages/desktop-client/src/components/accounts/Balance.jsx (2)

157-165: Remove commented-out code.

The commented-out code on line 157 should be removed as it's no longer needed and can cause confusion.

-  // const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' });

217-223: Remove commented-out prop.

The commented-out transactionsQuery prop should be removed.

   <MoreBalances
     accountId={accountId}
-    // transactionsQuery={transactionsQuery}
     balanceQuery={balanceQuery}
   />
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)

769-776: Consider showing a loading indicator instead of returning null.

The loading state handling is correct and prevents potential errors, but the user experience could be improved.

Consider replacing the null return with a loading indicator:

  if (isLoading) {
-   return null;
+   return <LoadingSpinner size="small" />;
  }

Also, consider using optional chaining for safer schedule lookup:

- const schedule = scheduleId ? schedules.find(s => s.id === scheduleId) : null;
+ const schedule = scheduleId ? schedules?.find(s => s.id === scheduleId) : null;

Line range hint 1765-1775: Document the isLoading prop type.

The loading state handling is correctly implemented, but the new prop should be documented for better maintainability.

Consider adding prop types documentation at the top of the component:

+ /**
+  * @typedef {Object} TransactionTableInnerProps
+  * @property {boolean} isLoading - Indicates whether transaction data is being loaded
+  */

/**
 * @param {TransactionTableInnerProps} props
 */
function TransactionTableInner({
  isLoading,
  tableNavigator,
  ...

Also applies to: 1994-1994

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c720427 and 12f571f.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (14)
  • packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/desktop-client/src/components/accounts/Header.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (16)
packages/desktop-client/src/components/accounts/Balance.jsx (4)

Line range hint 1-14: LGTM! Import cleanup and component updates look good.

The imports have been properly cleaned up, and the switch to ButtonWithLoading aligns with the loading state management improvements.


Line range hint 90-94: LGTM! Improved schedule balance calculation logic.

The account comparison and balance calculation now correctly handle transactions between different accounts.


117-123: LGTM! Improved null handling in FilteredBalance.

The addition of the || 0 fallback ensures the balance always displays a valid number.


71-77: ⚠️ Potential issue

Initialize scheduleBalance to prevent NaN issues.

The variable scheduleBalance is still initialized to null but is used in arithmetic operations. This can lead to NaN results.

Apply this fix:

-  let scheduleBalance = null;
+  let scheduleBalance = 0;

Likely invalid or redundant comment.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4)

2-8: LGTM! Good type safety improvements.

The changes improve type safety by:

  • Making the schedules array readonly to enforce immutability
  • Adding proper typing for the loading state

Also applies to: 39-40


261-261: LGTM! Consistent immutability.

The readonly type for items array maintains immutability consistency with the schedules prop.


278-343: LGTM! Good performance optimization with useCallback.

The renderSchedule function is properly memoized with a complete dependencies array.

🧰 Tools
🪛 Biome

[error] 325-325: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


345-375: LGTM! Good performance optimization with useCallback.

The renderItem function is properly memoized with the correct dependency.

Also applies to: 406-406

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

8-8: LGTM: Clean integration of the useCategories hook.

The hook integration is clean and follows React best practices by destructuring the list property from the hook's return value.

Also applies to: 158-159


313-313: LGTM: Proper dependency array update.

The dependency array correctly includes categories for the getImportPreview callback.


789-789: LGTM: Proper prop passing to Transaction component.

The categories prop is correctly passed to the Transaction component.


298-301: 🛠️ Refactor suggestion

Optimize category name lookup for existing transactions.

Similar to the previous issue, the find operation here could be optimized.

Use a Map for efficient lookups:

+const categoryNameMap = new Map(categories.map(cat => [cat.id, cat.name]));
-existing_trx.category = categories.find(
-  cat => cat.id === existing_trx.category
-)?.name;
+existing_trx.category = categoryNameMap.get(existing_trx.category) || null;

Likely invalid or redundant comment.


243-246: 🛠️ Refactor suggestion

Optimize category lookup performance.

The current implementation of parseCategoryFields performs a linear search through categories for each transaction. This could be inefficient for large datasets.

Consider using a Map for O(1) lookup:

+const categoryMap = new Map(categories.map(cat => [cat.name, cat.id]));
 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;
+  if (!trans.category) return null;
+  return categoryMap.get(trans.category) || null;
 }

Likely invalid or redundant comment.


587-588: 🛠️ Refactor suggestion

Consider handling edge cases in category processing.

The category processing during import lacks validation for edge cases.

Add validation and error handling:

-      const category_id = parseCategoryFields(trans, categories);
-      trans.category = category_id;
+      try {
+        const category_id = parseCategoryFields(trans, categories);
+        trans.category = category_id;
+      } catch (error) {
+        console.warn(`Failed to process category for transaction on ${trans.date}:`, error);
+        // Allow import to continue without category
+        trans.category = null;
+      }

Likely invalid or redundant comment.

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

827-827: LGTM: Loading state implementation looks good

The implementation correctly:

  • Provides a default value of false for the isLoading prop
  • Uses the new ButtonWithLoading component to show loading state
  • Preserves existing functionality while adding loading indicator support

Also applies to: 842-856


819-819: ⚠️ Potential issue

Make isLoading optional in SelectedItemsButtonProps

The isLoading property should be optional since it has a default value and to maintain backward compatibility.

Apply this diff:

-  isLoading: boolean;
+  isLoading?: boolean;

Likely invalid or redundant comment.

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 and nitpick comments (11)
packages/desktop-client/src/components/accounts/Balance.jsx (2)

157-157: Remove commented out code.

These commented lines about transactionsQuery should be removed as they're no longer needed and can cause confusion.

-  // const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' });
-          // transactionsQuery={transactionsQuery}

Also applies to: 220-220


Line range hint 1-231: Well-structured component hierarchy with clear responsibilities.

The component hierarchy is well-organized with clear separation of concerns:

  • DetailedBalance: Handles the visual presentation of balances
  • SelectedBalance: Manages selected transaction calculations
  • FilteredBalance: Displays filtered balance information
  • MoreBalances: Shows cleared/uncleared totals
  • Balances: Orchestrates the overall balance display

The migration to functional components and the introduction of proper prop types has improved the maintainability of the code.

Consider splitting these components into separate files if they grow more complex in the future, as they each have distinct responsibilities.

packages/loot-core/src/shared/transactions.ts (6)

163-163: Consider strengthening return type safety.

The function implementation looks good, but consider making the return type more specific by using a discriminated union type for the newTransaction property to better handle the _deleted flag case.

type TransactionResult = 
  | { _deleted: true } & TransactionEntity 
  | (TransactionEntity | TransactionEntityWithError);

interface ReplaceTransactionsResult {
  data: TransactionEntity[];
  newTransaction: TransactionResult | null;
  diff: ReturnType<typeof diffItems<TransactionEntity>>;
}

248-248: Consider making property updates more generic.

The function correctly handles readonly arrays, but the payee update logic could be made more generic to handle other properties that might need similar parent-child synchronization.

// Consider refactoring to something like this:
const syncedProps = ['payee', 'date'] as const;
const child = {
  ...t,
  ...Object.fromEntries(
    syncedProps
      .filter(prop => t[prop] === trans[prop])
      .map(prop => [prop, transaction[prop]])
  )
};

281-281: Consider enhancing error handling.

While the function correctly handles readonly arrays, consider adding validation for edge cases:

  1. When deleting the last child transaction
  2. When the parent transaction is not found
if (!trans) {
  throw new Error(`Transaction not found: ${id}`);
}
if (trans.is_parent && !trans.subtransactions?.length) {
  throw new Error(`Parent transaction ${id} has no subtransactions`);
}

306-306: Consider making error calculation more explicit.

The function correctly handles readonly arrays, but the error calculation could be more explicit about the expected balance.

const expectedBalance = num(trans.amount);
const actualBalance = 0; // Initial split has no amounts
return {
  ...trans,
  is_parent: true,
  error: expectedBalance === actualBalance ? null : SplitTransactionError(actualBalance, trans),
  // ... rest of the code
};

334-334: Consider adding type guard for temporary transactions.

While the function handles readonly arrays correctly, consider adding a type guard to ensure only temporary transactions are processed.

function isTemporaryTransaction(t: TransactionEntity): boolean {
  return isTemporaryId(t.id);
}

export function realizeTempTransactions(
  transactions: readonly TransactionEntity[],
): TransactionEntity[] {
  if (!transactions.every(isTemporaryTransaction)) {
    throw new Error('All transactions must be temporary');
  }
  // ... rest of the code
}

355-356: Consider making amount calculation more robust.

While the function correctly handles readonly arrays, the amount calculation could be more robust by handling null/undefined amounts.

const totalAmount = remainingChildTransactions
  .map(t => num(t.amount))
  .reduce((total, amount) => total + amount, 0);

const updatedParentTransaction = {
  ...parentTransaction,
  ...(!deleteParentTransaction ? { amount: totalAmount } : {}),
};
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)

Line range hint 1817-1827: Consider adding type checking and default value for the isLoading prop.

The isLoading prop is correctly added but could benefit from better type safety and default value handling.

+ import PropTypes from 'prop-types';
// ... or if using TypeScript:
+ interface TransactionTableInnerProps {
+   isLoading?: boolean;
+   // ... other props
+ }

function TransactionTableInner({
-  isLoading,
+  isLoading = false,
  tableNavigator,
  tableRef,
  // ... other props
}) {

2052-2052: Consider enhancing the loading state visual feedback.

While the loading prop is correctly passed to the Table component, consider adding a loading indicator or skeleton UI to improve user experience during data fetching.

<Table
-  loading={isLoading}
+  loading={isLoading}
+  loadingComponent={
+    <View style={{ padding: 20, alignItems: 'center' }}>
+      <LoadingSpinner size="medium" />
+      <Text style={{ marginTop: 10 }}>Loading transactions...</Text>
+    </View>
+  }
  navigator={tableNavigator}
packages/loot-core/src/client/query-hooks.ts (1)

Line range hint 27-34: Prevent state updates on unmounted components in onError callback

The onError callback in the liveQuery does not check the isUnmounted flag before calling setError. This could lead to state updates on an unmounted component, causing memory leaks or React warnings.

Apply this diff to add the isUnmounted check:

 let live: null | LiveQuery<Response> = liveQuery<Response>(query, {
   onData: data => {
     if (!isUnmounted) {
       setData(data);
       setIsLoading(false);
     }
   },
-  onError: setError,
+  onError: error => {
+    if (!isUnmounted) {
+      setError(error);
+    }
+  },
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 12f571f and 6898f76.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3708.md is excluded by !**/*.md
📒 Files selected for processing (17)
  • packages/desktop-client/src/components/accounts/Balance.jsx (8 hunks)
  • packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
  • packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (1 hunks)
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1 hunks)
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
  • packages/desktop-client/src/components/table.tsx (4 hunks)
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (1 hunks)
  • packages/loot-core/src/client/query-hooks.ts (1 hunks)
  • packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • packages/desktop-client/src/components/accounts/Header.tsx
  • packages/desktop-client/src/components/accounts/Reconcile.tsx
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx
  • packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx
  • packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
  • packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
  • packages/desktop-client/src/components/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/desktop-client/src/hooks/useSelected.tsx
  • packages/loot-core/src/client/data-hooks/schedules.tsx
🧰 Additional context used
📓 Learnings (1)
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-11-10T16:45:25.627Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🔇 Additional comments (11)
packages/desktop-client/src/components/accounts/Balance.jsx (3)

117-126: LGTM! Good defensive programming.

The addition of the || 0 fallback ensures that the balance display won't break with null/undefined values.


158-165: LGTM! Good use of memoization.

The balanceBinding is properly memoized with the correct dependencies (accountId and balanceQuery), which helps prevent unnecessary recalculations.


90-93: Verify the schedule balance calculation logic.

The condition for calculating schedule balance has been modified to use accountId. Please ensure this change maintains the correct accounting direction (credit/debit) for scheduled transactions across different accounts.

✅ Verification successful

Schedule balance calculation correctly handles accountId changes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other schedule balance calculations in the codebase
ast-grep --pattern 'scheduleBalance $op getScheduledAmount($amount)'

Length of output: 70


Script:

#!/bin/bash
# Search for scheduleBalance being incremented or decremented with getScheduledAmount
ast-grep --pattern 'scheduleBalance ($op:+= | -:= ) getScheduledAmount($amount)'

Length of output: 82


Script:

#!/bin/bash
# Search for scheduleBalance being incremented or decremented with getScheduledAmount
rg 'scheduleBalance\s*[\+\-]=\s*getScheduledAmount\('

Length of output: 291

packages/loot-core/src/shared/transactions.ts (5)

93-96: LGTM! Note the sorting dependency.

The readonly modifier is appropriate for this function. Just note that this function relies on transactions being sorted with parents before children, which is enforced at the database layer.


110-113: LGTM! Readonly modifier correctly applied.

The function properly handles readonly transactions array while extracting split transactions.


123-125: LGTM! Immutability properly maintained.

The function correctly preserves immutability by creating a new array while ungrouping transactions.


139-139: LGTM! Immutable grouping implementation.

The function properly handles readonly array while creating a new grouped transaction object.


229-229: LGTM! Proper immutability handling.

The function correctly maintains immutability while adding split transactions.

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

827-827: LGTM: Loading state implementation looks good

The changes properly implement loading state handling:

  • Default value for isLoading is set to false
  • ButtonWithLoading is used correctly with the isLoading prop

Also applies to: 842-856


819-819: ⚠️ Potential issue

Make isLoading optional in SelectedItemsButtonProps

Since you've provided a default value of false for isLoading in the function signature, you should make it optional in the type definition to maintain backward compatibility.

Apply this diff:

-  isLoading: boolean;
+  isLoading?: boolean;

Likely invalid or redundant comment.

packages/loot-core/src/client/query-hooks.ts (1)

Line range hint 25-38: Use useRef for isUnmounted to prevent stale closure issues

Using a local variable isUnmounted inside useEffect can lead to stale closure issues. Consider using useRef to ensure the flag remains consistent across renders.

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.

2 participants