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

useTransactions hook to simplify loading of transactions #3685

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

Conversation

joel-jeremy
Copy link
Contributor

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

Create a new useTransactions hook to simplify loading of transactions and preview transactions.

The mobile transactions page have been migrated to use the new hook. Will try to use it on the desktop transaction list as well if I am able to find time because it first will need to be migrated to a functional component.

Having both desktop and mobile use the same hook to load the transactions would make the behavior more consistent on both views and would be easier to maintain.

@actual-github-bot actual-github-bot bot changed the title useTransactions hook to load transactions [WIP] useTransactions hook to load transactions Oct 17, 2024
Copy link

netlify bot commented Oct 17, 2024

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit d787213
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/67242bed543ef1000891b52e
😎 Deploy Preview https://deploy-preview-3685.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 17, 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 → 8 5.32 MB → 5.32 MB (+5.84 kB) +0.11%
Changeset
File Δ Size
home/runner/work/actual/actual/packages/loot-core/src/client/data-hooks/transactions.ts 🆕 +4.22 kB 0 B → 4.22 kB
src/components/rules/ScheduleValue.tsx 📈 +423 B (+91.56%) 462 B → 885 B
home/runner/work/actual/actual/packages/loot-core/src/client/data-hooks/schedules.tsx 📈 +677 B (+28.15%) 2.35 kB → 3.01 kB
home/runner/work/actual/actual/packages/loot-core/src/client/query-hooks.ts 📈 +168 B (+23.97%) 701 B → 869 B
src/components/schedules/index.tsx 📈 +375 B (+11.36%) 3.22 kB → 3.59 kB
home/runner/work/actual/actual/packages/loot-core/src/client/data-hooks/widget.ts 📈 +26 B (+10.32%) 252 B → 278 B
home/runner/work/actual/actual/packages/loot-core/src/client/query-helpers.ts 📈 +520 B (+8.16%) 6.22 kB → 6.73 kB
home/runner/work/actual/actual/packages/loot-core/src/shared/query.ts 📈 +208 B (+7.21%) 2.82 kB → 3.02 kB
home/runner/work/actual/actual/packages/loot-core/src/client/data-hooks/dashboard.ts 📈 +12 B (+5.29%) 227 B → 239 B
src/components/modals/ScheduledTransactionMenuModal.tsx 📈 +86 B (+3.24%) 2.59 kB → 2.67 kB
home/runner/work/actual/actual/packages/loot-core/src/client/data-hooks/reports.ts 📈 +40 B (+3.21%) 1.22 kB → 1.26 kB
src/components/schedules/ScheduleLink.tsx 📈 +41 B (+1.11%) 3.61 kB → 3.65 kB
node_modules/lodash/debounce.js 📈 +56 B (+0.92%) 5.95 kB → 6 kB
src/components/ManageRules.tsx 📈 +69 B (+0.81%) 8.33 kB → 8.39 kB
node_modules/lodash/now.js 📈 +4 B (+0.79%) 505 B → 509 B
node_modules/lodash.debounce/index.js 📈 +68 B (+0.62%) 10.66 kB → 10.73 kB
src/components/mobile/accounts/Accounts.jsx 📈 +40 B (+0.55%) 7.15 kB → 7.18 kB
node_modules/lodash/_baseGetTag.js 📈 +4 B (+0.52%) 763 B → 767 B
src/components/accounts/Account.tsx 📈 +213 B (+0.47%) 43.91 kB → 44.12 kB
src/components/schedules/SchedulesTable.tsx 📈 +39 B (+0.35%) 10.84 kB → 10.88 kB
src/components/schedules/ScheduleDetails.jsx 📈 +72 B (+0.23%) 30.46 kB → 30.53 kB
src/components/modals/EditRuleModal.jsx 📈 +74 B (+0.18%) 39.37 kB → 39.44 kB
src/components/PrivacyFilter.tsx 📈 +6 B (+0.18%) 3.26 kB → 3.27 kB
src/components/transactions/TransactionsTable.jsx 📈 +28 B (+0.04%) 63.53 kB → 63.56 kB
src/components/accounts/Balance.jsx 📉 -7 B (-0.13%) 5.43 kB → 5.42 kB
home/runner/work/actual/actual/packages/loot-core/src/client/queries.ts 📉 -23 B (-0.31%) 7.3 kB → 7.27 kB
src/components/mobile/transactions/TransactionList.jsx 📉 -100 B (-0.62%) 15.8 kB → 15.7 kB
node_modules/lodash/_objectToString.js 📉 -4 B (-0.69%) 578 B → 574 B
node_modules/lodash/isSymbol.js 📉 -12 B (-1.78%) 673 B → 661 B
node_modules/lodash/_WeakMap.js 📉 -4 B (-2.11%) 190 B → 186 B
src/components/mobile/accounts/AccountTransactions.tsx 📉 -313 B (-3.93%) 7.78 kB → 7.48 kB
home/runner/work/actual/actual/packages/loot-core/src/client/data-hooks/filters.ts 📉 -45 B (-7.35%) 612 B → 567 B
src/hooks/usePreviewTransactions.ts 📉 -135 B (-8.05%) 1.64 kB → 1.5 kB
src/components/mobile/budget/CategoryTransactions.jsx 📉 -861 B (-22.74%) 3.7 kB → 2.86 kB
src/hooks/useSchedules.ts 🔥 -88 B (-100%) 88 B → 0 B
View detailed bundle breakdown

Added

No assets were added

Removed

Asset File Size % Changed
static/js/usePreviewTransactions.js 1.64 kB → 0 B (-1.64 kB) -100%

Bigger

Asset File Size % Changed
static/js/index.js 3.33 MB → 3.35 MB (+19.53 kB) +0.57%
static/js/wide.js 232.16 kB → 234.26 kB (+2.1 kB) +0.90%

Smaller

Asset File Size % Changed
static/js/ReportRouter.js 1.5 MB → 1.49 MB (-13.89 kB) -0.90%
static/js/narrow.js 82.57 kB → 82.3 kB (-273 B) -0.32%

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/AppliedFilters.js 21.3 kB 0%
static/js/BackgroundImage.js 122.29 kB 0%

Copy link
Contributor

github-actions bot commented Oct 17, 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 → 1.27 MB (+126 B) +0.01%
Changeset
File Δ Size
packages/loot-core/src/shared/query.ts 📈 +292 B (+8.11%) 3.51 kB → 3.8 kB
View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

Asset File Size % Changed
kcab.worker.js 1.27 MB → 1.27 MB (+126 B) +0.01%

Smaller

No assets were smaller

Unchanged

No assets were unchanged

@joel-jeremy joel-jeremy changed the title [WIP] useTransactions hook to load transactions [WIP] useTransactions hook to simplify loading of transactions Oct 17, 2024
@joel-jeremy joel-jeremy force-pushed the useTransactions-hook branch 2 times, most recently from b83c030 to 2fe41da Compare October 18, 2024 01:21
@joel-jeremy joel-jeremy changed the title [WIP] useTransactions hook to simplify loading of transactions useTransactions hook to simplify loading of transactions Oct 18, 2024
Copy link
Contributor

coderabbitai bot commented Oct 18, 2024

Walkthrough

The pull request introduces substantial changes across multiple components and files, primarily enhancing transaction management and state handling. In AccountTransactions.tsx, the useDefaultSchedulesQueryTransform hook is replaced with a useMemo call that utilizes accountSchedulesQuery, altering the schedule querying method. New hooks, namely useTransactions, usePreviewTransactions, and useTransactionsSearch, are introduced to streamline transaction fetching and searching processes. The SchedulesProvider is updated to accept a query prop instead of a transform prop, improving the query structure. Several functions are refactored to utilize useCallback, enhancing performance through memoization, and method signatures for AccountTransactions and TransactionListWithPreviews are modified to accommodate broader prop types.

In CategoryTransactions.jsx, the manual pagination system is replaced with a hook-based approach, simplifying transaction management. The makeRootQuery function is renamed to baseTransactionsQuery, and the logic for updating queries is streamlined.

The new transactions.ts file introduces the useTransactions, useTransactionsSearch, and usePreviewTransactions hooks, encapsulating logic for handling transaction data retrieval, search functionality, and error management, thereby enhancing the overall structure and readability of transaction management.

Suggested labels

sparkles: Merged

Suggested reviewers

  • youngcw

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

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

Line range hint 1-14: Summary: Good refactoring, consider documenting impact

Overall, the changes in this file appear to be part of a larger refactoring effort to replace useLiveQuery with useQuery. The modifications are consistent and maintain the existing functionality while improving type safety.

However, it would be beneficial to add a comment explaining the rationale behind this change, particularly addressing any potential impact on real-time update behavior. This documentation would be valuable for future maintenance and understanding of the system's behavior.

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

15-18: LGTM: Improved return value consistency and memoization.

The changes enhance the clarity and reliability of the hook:

  1. Explicit isLoading state in the return object improves usability.
  2. data: queryData || [] ensures an array is always returned, preventing potential null pointer exceptions.
  3. Updated useMemo dependencies correctly reflect the reliance on both isLoading and queryData.

Consider using nullish coalescing for a slight optimization:

data: queryData ?? [],

This change would only default to an empty array if queryData is null or undefined, preserving falsy values like an empty array if that's what the query returns.

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

8-10: Improved query specificity and dependency tracking.

The implementation of useQuery has been improved:

  1. The query now filters by both id and type, making it more specific.
  2. The dependency array correctly includes both id and type.
  3. The destructured return value now includes isLoading, which is used later.

These changes enhance the hook's functionality and reactivity.

Consider using an object for the filter to improve readability:

() => q('dashboard').filter({ id, type }).select('*'),
packages/desktop-client/e2e/page-models/mobile-accounts-page.js (2)

11-13: LGTM: Enhances test reliability

The waitFor() method is a valuable addition that improves test reliability by ensuring the account list is ready before proceeding. This aligns well with the PR's objective of simplifying transaction loading.

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

 async waitFor() {
-  await this.accountList.waitFor();
+  try {
+    await this.accountList.waitFor({ timeout: 5000 });
+  } catch (error) {
+    throw new Error('Account list did not appear within 5 seconds');
+  }
 }

Line range hint 1-38: Consider leveraging waitFor() in existing methods

The new waitFor() method is a great addition for ensuring the account list is ready. Consider using it in the existing getNthAccount() and openNthAccount() methods to further improve their reliability:

 async getNthAccount(idx) {
+  await this.waitFor();
   const accountRow = this.accounts.nth(idx);
   // ... rest of the method
 }

 async openNthAccount(idx) {
+  await this.waitFor();
   await this.accounts.nth(idx).click();
   // ... rest of the method
 }

This change would ensure that the account list is always ready before attempting to interact with specific accounts, potentially preventing race conditions in tests.

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

19-20: LGTM! Consider using TypeScript's non-null assertion.

The updated use of the useSchedules hook looks good. The default value for schedules and the addition of isLoading improve the component's robustness.

Consider using TypeScript's non-null assertion operator to avoid the need for a default value:

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

This assumes that schedules will never be undefined when it's not loading, which seems to be the case given the current implementation.


21-23: LGTM! Consider adding a loading indicator.

The addition of the isLoading check is a good improvement in handling the loading state of the data.

For a better user experience, consider replacing the null with a loading indicator:

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

This would provide visual feedback to the user that data is being loaded.

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

18-20: LGTM! Consider a more specific method name.

The waitFor() method is a good addition to ensure the transaction list is loaded before interacting with it. This can help prevent race conditions in your e2e tests.

Consider renaming the method to something more specific, like waitForTransactionList(), to make its purpose clearer.


36-38: LGTM! Consider adding a JSDoc comment for consistency.

The clearSearch() method is a valuable addition, complementing the existing searchByText() method. It provides a clean way to reset the search state in your tests.

For consistency with other methods in this class, consider adding a JSDoc comment to describe the method's purpose. For example:

/**
 * Clear the search box
 */
async clearSearch() {
  await this.searchBox.clear();
}

18-20: Great additions to enhance e2e testing capabilities!

The new waitFor() and clearSearch() methods are valuable additions to the MobileAccountPage class. They will improve the reliability and flexibility of your e2e tests, especially when working with transaction lists and search functionality. These changes align well with the PR objectives of simplifying transaction handling and will likely be useful when implementing tests for the new useTransactions hook.

As you continue to develop your e2e testing framework, consider creating a base Page class with common methods like waitFor(). This could help reduce code duplication if you find yourself adding similar methods to other page model classes.

Also applies to: 36-38

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

Line range hint 39-60: Approve changes with a minor optimization suggestion.

The modifications to the useReports function are well-implemented:

  1. Destructuring data and isLoading from useQuery provides better control over the loading state.
  2. The conditional spreading of queryData ensures safe handling of null or undefined data.
  3. The useMemo dependency array has been correctly updated to reflect the new variables.

These changes improve the overall robustness of the function.

Consider a minor optimization to avoid unnecessary array creation:

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

This change would avoid creating a new array when queryData is truthy, potentially improving performance slightly for large datasets.


Line range hint 1-61: Summary: Changes align with PR objectives but warrant further clarification.

The modifications in this file, particularly the switch from useLiveQuery to useQuery and the updates to the useReports function, appear to be part of a larger effort to simplify data loading as mentioned in the PR objectives. While these changes don't directly implement the useTransactions hook, they seem to follow a similar pattern of simplifying data fetching and management.

However, it's important to note that this file deals with reports rather than transactions. To ensure full alignment with the PR objectives:

  1. Clarify if these changes are intended to be part of the same data loading simplification strategy as the useTransactions hook.
  2. Confirm that the switch from useLiveQuery to useQuery doesn't negatively impact the real-time nature of the reports, if that was a requirement.
  3. Consider documenting the rationale behind these changes in the PR description or in code comments to maintain clarity for future developers.

If this pattern of switching from useLiveQuery to useQuery is being applied across multiple features, consider creating a central document or ADR (Architecture Decision Record) explaining the reasoning and implications of this architectural change.

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

41-42: Good addition of waitFor() call and improved readability

Adding await accountsPage.waitFor(); here is consistent with the previous test and provides the same benefits of ensuring the page is fully loaded before proceeding.

The empty line improves readability by clearly separating the navigation and waiting steps from the next action. For even better consistency, consider adding a similar empty line after the waitFor() call in the previous test as well.


44-44: Good addition of waitFor() call for individual account page

Adding await accountPage.waitFor(); after opening an individual account page is a great improvement. It ensures that the individual account page is fully loaded before proceeding with assertions, maintaining consistency with the previous waitFor() calls and following the same best practice.

For consistency in code style, consider adding an empty line after this waitFor() call, similar to what was done in the previous test case.


57-59: Good additions for search clearing and verification

The changes here improve the test coverage and readability:

  1. Adding clearSearch() is a good way to reset the search state.
  2. The assertion await expect(accountPage.transactions).not.toHaveCount(0); verifies that clearing the search actually restores the transaction list.
  3. The empty line improves readability by separating different test steps.

These additions make the test more robust and easier to understand. For consistency, consider adding a comment explaining the purpose of this section, such as "// Verify that clearing the search restores all transactions".

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

19-29: State management improvements look good, with a minor suggestion.

The changes to allow null for previousScheduleData and the reorganization of scheduleData initialization improve the code structure and state handling. These modifications align with best practices for React hooks.

However, to further improve code clarity, consider using a more descriptive name for the scheduleData?.isLoading check.

Consider renaming the condition for better readability:

-if (scheduleData?.isLoading) {
+if (scheduleData?.isLoading ?? true) {
   return [];
 }

This change explicitly handles the case where scheduleData might be undefined.


Line range hint 30-75: Logic changes improve performance, with a suggestion for error handling.

The addition of the scheduleData !== previousScheduleData check is a good optimization that prevents unnecessary re-processing of transactions. This change aligns with the PR's goal of simplifying transaction loading and can improve overall performance.

The core logic for processing transactions remains intact, which is good for maintaining the function's primary purpose.

Consider adding error handling to the Promise chain:

 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 processing transactions:', error);
+  // Consider setting an error state or showing a user-friendly error message
 });

This addition will help catch and log any errors that occur during transaction processing, improving the robustness of the code.


Line range hint 77-84: Consider the future of the isForPreview helper function.

While the isForPreview function remains unchanged and functional, it's worth noting that it's being used in a now-deprecated hook. As part of the deprecation process, consider whether this helper function should be moved or refactored for use with the new useTransactions hook, or if it will be phased out entirely.

If this function is still needed, consider moving it to a shared utility file where it can be used by both the deprecated and new hooks during the transition period.

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

43-48: Improved loading state handling and data access!

The changes in loading state handling are well-implemented:

  1. The new conditional rendering based on isSchedulesLoading prevents rendering when data is not yet available.
  2. Defining schedule after the loading check ensures it's only accessed when data is available.
  3. The optional chaining in schedules?.[0] safely handles potential undefined values.

These changes improve the robustness of the component.

Consider adding a loading indicator or placeholder content instead of returning null during loading, to improve user experience:

 if (isSchedulesLoading) {
-  return null;
+  return <LoadingIndicator />; // Or any appropriate loading UI
 }

68-68: Safe rendering of schedule date implemented, but consider improving user feedback.

The change in rendering the schedule date is a good improvement in terms of error prevention:

  1. The use of optional chaining (?.) prevents errors if schedule is undefined.
  2. Providing an empty string as a fallback ensures the format function doesn't receive undefined.

This change is consistent with the earlier modifications to handle potential undefined values.

However, rendering an empty string for the date might not provide the best user experience. Consider providing a more informative fallback:

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

This way, users will see a clear message if the date is missing, rather than an empty space.

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

65-66: Approved: Good addition to improve test reliability.

Adding await accountPage.waitFor(); after navigating to the account page is a good practice. It ensures that the page is fully loaded before proceeding with the test, which can prevent flaky tests due to timing issues.

For consistency, consider adding a comment explaining why this wait is necessary, e.g.:

// Ensure the account page is fully loaded before proceeding
await accountPage.waitFor();

This will help other developers understand the purpose of this wait call.


114-114: Approved: Consistent improvement in test reliability.

Adding await accountPage.waitFor(); after creating a new account is a good practice. It ensures that the account page is fully loaded before proceeding with the import tests, which can prevent flaky tests due to timing issues.

For consistency with the previous suggestion and to improve code readability, consider adding a comment explaining the purpose of this wait:

// Ensure the newly created account page is fully loaded before proceeding with import tests
await accountPage.waitFor();

This comment will help other developers understand the reason for this wait call.

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

71-76: Improved data handling with useCachedSchedules hook

The changes to the useCachedSchedules hook usage are well-implemented. The addition of the isLoading check prevents rendering before data is available, which is a good practice for handling asynchronous data in React.

Consider using the nullish coalescing operator for a more concise default value assignment:

const { isLoading, schedules } = useCachedSchedules();
const safeSchedules = schedules ?? [];

This approach maintains the same functionality while slightly improving readability.

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

33-35: LGTM! Consider minor enhancements.

The new waitFor() method is a good addition to ensure the transaction table is ready before proceeding with other actions. This aligns well with the PR objectives of simplifying transaction loading.

Consider the following minor enhancements:

  1. Add a timeout parameter to make the wait time configurable:
async waitFor(timeout = 30000) {
  await this.transactionTable.waitFor({ timeout });
}
  1. Add a comment explaining the purpose of this method:
/**
 * Waits for the transaction table to be ready for interaction.
 * @param {number} timeout - Maximum time to wait in milliseconds.
 */
async waitFor(timeout = 30000) {
  await this.transactionTable.waitFor({ timeout });
}
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)

33-33: Summary: Improved immutability with minimal impact

The changes to make schedules prop and items array readonly improve type safety and enforce immutability, which are good practices in React development. These modifications don't appear to affect the component's functionality directly, as they only change type declarations without altering the component's logic.

However, to ensure full compatibility and consistency across the codebase, consider the following follow-up actions:

  1. Review any components that pass data to SchedulesTable to ensure they're not expecting to mutate the schedules prop.
  2. Check if there are any other similar components that could benefit from the same immutability improvements.
  3. Consider adding unit tests to verify that the component behaves correctly with the new readonly types.

To further improve the codebase:

  1. Consider applying similar readonly modifiers to props and state in other components where immutability is desired.
  2. If not already in place, consider using a linter rule (e.g., eslint-plugin-react-hooks/exhaustive-deps) to ensure all dependencies are correctly specified in the useMemo hook's dependency array.

Also applies to: 253-253

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

Line range hint 297-332: Summary: Successful adaptation of tests to new paged query API.

The changes in this file consistently update the test assertions to use the new totalCount and hasNext properties of the paged query object. These modifications align well with the API changes described in the summary, replacing getTotalCount() with totalCount and isFinished() with hasNext.

The updated tests maintain their original structure and logic while adapting to the new API, ensuring that the paged query functionality is still thoroughly tested. This consistent update across the test file helps maintain the integrity of the test suite and validates the correct implementation of the new API.

Consider updating the documentation or adding inline comments to highlight these API changes, especially if they are part of a public interface. This will help other developers understand the new usage pattern more quickly.

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

71-72: Consider displaying a loading indicator when schedules are loading

Returning null while schedules are loading might result in a blank screen, which could confuse users. To improve user experience, consider displaying a loading indicator.

Apply this diff to display a simple loading message:

if (isSchedulesLoading) {
-   return null;
+   return <View><Trans>Loading schedules...</Trans></View>;
}
packages/loot-core/src/shared/query.ts (1)

166-168: Usage of Short Function Name 'q'

The function q serves as a shorthand for creating new Query instances. Consider whether a more descriptive name would improve code readability, especially in a larger codebase.

If you decide to rename the function for clarity, apply this diff:

-export function q(table: QueryState['table']) {
+export function createQuery(table: QueryState['table']) {
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3)

1-1: Organize import statements and remove unused imports

The import statements could be organized for better readability. Additionally, ensure that all imported modules are used within the code. If there are unused imports, consider removing them to keep the code clean.

Also applies to: 7-7


35-43: Destructure only necessary properties from useTransactions

You're destructuring several properties from the useTransactions hook. Verify that all of these properties (transactions, isLoading, loadMoreTransactions, reloadTransactions, updateTransactionsQuery) are used within the component. Removing any unused properties can help keep the code concise.


81-86: Redundant useCallback wrapping around onSearch

The onSearch function is wrapped with useCallback, but since it only calls updateSearchQuery, which is already memoized, this may be unnecessary. Consider removing the useCallback to simplify the code unless there are performance concerns that justify its use.

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

320-321: Consider displaying a loading indicator while schedules are loading

Currently, the component returns null when schedules are loading, which may provide no visual feedback to the user. Consider displaying a loading indicator or placeholder content to improve user experience during data fetching.


Line range hint 460-460: Simplify parameter naming by removing unnecessary aliasing

The onSave parameter is currently aliased as originalOnSave, but aliasing isn't necessary if there are no naming conflicts. Consider using onSave directly to simplify the code.

Apply this diff to simplify the parameter:

- export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
+ export function EditRuleModal({ defaultRule, onSave }) {

And update the function call:

- originalOnSave?.(rule);
+ onSave?.(rule);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0d9528e and 13d8a9e.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
📒 Files selected for processing (34)
  • .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/Account.tsx (14 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (7 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/ScheduledTransactionMenuModal.tsx (2 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (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/transactions/TransactionsTable.jsx (1 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/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 (4 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)
💤 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 due to trivial changes (1)
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
🧰 Additional context used
📓 Learnings (2)
packages/desktop-client/src/components/accounts/Account.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/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/loot-core/src/client/query-helpers.ts

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)


[error] 84-84: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

🔇 Additional comments (66)
packages/desktop-client/src/hooks/useNotes.ts (3)

3-3: LGTM: Import statement correctly added

The new import statement for useQuery is correctly added and aligns with its usage in the hook.


Line range hint 12-12: LGTM: Return statement properly updated

The return statement logic is correctly maintained, and the useMemo hook is properly used for performance optimization. The dependency array for useMemo is correctly updated to use data.


8-11: Confirm impact on real-time updates

The change from useLiveQuery to useQuery looks good and maintains the same query logic. The added type annotation <NoteEntity> improves type safety.

However, could you please clarify if this change affects the real-time update behavior of the hook? If there are any implications, consider adding a comment explaining the rationale behind this change.

To verify the usage and impact of this change, let's run the following script:

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

5-5: LGTM: Import statement updated correctly.

The import of useQuery from '../query-hooks' aligns with the function's implementation change. This local import promotes maintainability.


8-11: Verify the type annotation for the query result.

The change from useLiveQuery<Widget[]> to useQuery<Widget> might be unintended. The query q('dashboard').select('*') suggests it's fetching multiple widgets, so the type should likely remain Widget[].

Please confirm if this type change is intentional. If not, consider updating it to:

const { data: queryData, isLoading } = useQuery<Widget[]>(
  () => q('dashboard').select('*'),
  [],
);

Line range hint 1-20: Overall, good improvements to the useDashboard hook.

The changes in this file align well with the PR objectives of simplifying transaction loading. The switch to useQuery and the improved handling of loading states contribute to this goal. The code is now more explicit about its loading state and ensures consistent return types, which should make it easier to use in components.

Just ensure the type annotation for the query result is correct, as flagged in a previous comment.

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

7-7: Improved type safety in function signature.

The change from string to W['id'] for the id parameter enhances type safety by ensuring that the id matches the specific Widget type. This is a positive improvement that aligns the function signature more closely with the Widget type.


15-18: Improved memoization with loading state.

The changes to the useMemo implementation are beneficial:

  1. The isLoading state is now directly used from the useQuery hook, providing more accurate loading information.
  2. The memoization dependencies correctly include isLoading, ensuring the memoized value updates when the loading state changes.

These updates improve the hook's reactivity and provide more accurate information to consumers of this hook.


5-5: Verify the impact of switching from useLiveQuery to useQuery.

The change from useLiveQuery to useQuery suggests a shift in the data fetching mechanism. This could potentially affect real-time updates if useQuery doesn't provide live updates.

Please run the following script to check for any remaining usages of useLiveQuery and ensure this change is consistent across the codebase:

✅ Verification successful

Verification successful: No remaining usages of useLiveQuery found in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for remaining usages of useLiveQuery
# Expected result: No occurrences of useLiveQuery, unless intentionally kept

rg --type typescript --type javascript 'useLiveQuery'

Length of output: 88


Script:

#!/bin/bash
# Description: Check for remaining usages of useLiveQuery in .ts and .js files
# Expected result: No occurrences of useLiveQuery, unless intentionally kept

rg 'useLiveQuery' --glob '*.ts' --glob '*.js'

Length of output: 45

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

7-7: LGTM: Good addition for improved test interaction

The new accountList property provides a clear way to interact with the account list in tests. Using getByLabel is a good practice for maintaining accessibility in your selectors.

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

3-3: LGTM! Verify consistent usage across the codebase.

The updated import statement for useSchedules looks good. It appears to be part of a refactoring effort to improve code organization.

To ensure consistency, let's verify the usage of this import across the codebase:

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

30-30: Excellent addition of waitFor() call

Adding await accountsPage.waitFor(); after navigating to the accounts page is a great improvement. This ensures that the page is fully loaded before proceeding with assertions, which can significantly reduce test flakiness caused by race conditions. It's a best practice in end-to-end testing, especially for single-page applications where content might be loaded asynchronously.


Line range hint 1-64: Overall excellent improvements to test reliability and readability

The changes made to this test file significantly enhance its reliability and readability:

  1. The addition of waitFor() calls ensures that pages are fully loaded before interactions or assertions, reducing potential flakiness.
  2. The new clearSearch() method and associated assertion improve test coverage for the search functionality.
  3. The added empty lines improve code readability by clearly separating different test steps.

These improvements align well with the PR objectives of enhancing transaction handling, as they ensure that the tests accurately reflect the expected behavior of the accounts and transactions pages. The changes follow best practices in end-to-end testing and will contribute to a more stable and maintainable test suite.

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

13-15: Deprecation notice is clear and helpful.

The added deprecation notice effectively communicates that this hook should no longer be used and provides a clear alternative. This aligns well with the PR objective of simplifying transaction loading.

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

36-41: Great improvements in hook usage and performance optimization!

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

  1. Destructuring isLoading and schedules improves code readability.
  2. Memoizing the queryBuilder callback with useCallback is a good practice for performance optimization.
  3. The dependency array for useCallback correctly includes scheduleId.

These changes enhance both the clarity and efficiency of the code.


54-54: Safe rendering of schedule name implemented.

The change in rendering the schedule name is a good improvement:

  1. The use of optional chaining (?.) prevents errors if schedule is undefined.
  2. Providing an empty string as a fallback ensures the component doesn't break if the name is missing.

This change aligns well with the earlier modifications to handle potential undefined values, making the component more robust.


Line range hint 1-132: Overall, excellent improvements to component robustness and error handling.

The changes in this file significantly enhance the ScheduledTransactionMenuModal component:

  1. Improved hook usage with destructuring and memoization.
  2. Better loading state management.
  3. Safer data access using optional chaining.

These modifications align well with React best practices and make the component more reliable. The suggestions provided in the review comments (adding a loading indicator and improving date fallback) would further enhance the user experience.

Great work on refactoring this component!

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

42-42: Improved naming convention for query filtering

The change from transform to queryBuilder in the useSchedules hook is a positive improvement. This new naming convention more accurately describes the property's purpose, which is to build a query for filtering schedules. It enhances code readability and aligns well with the PR's objective of simplifying transaction loading.

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

Line range hint 1-35: Summary: Good addition that enhances test reliability.

The new waitFor() method is a valuable addition to the AccountPage class. It aligns well with the PR objectives of simplifying transaction loading and has the potential to enhance the reliability of e2e tests. Consider the suggested minor improvements and integrating this method into other relevant parts of the class for maximum benefit.

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

31-34: Improved function signature and naming.

The changes to this function enhance both clarity and type safety:

  1. Renaming from getAccountFilter to accountFilter follows a more concise naming convention.
  2. The updated accountId parameter type (AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized') improves type safety by explicitly defining allowed values.

These modifications make the function more robust and easier to use correctly.


70-72: Improved function signature, naming, and consistency.

The changes to this function enhance clarity, type safety, and maintain consistency:

  1. Renaming from makeTransactionsQuery to transactions follows a more concise naming convention.
  2. The updated accountId parameter type matches that of accountFilter, improving type safety and consistency.
  3. The function body has been correctly updated to use the renamed accountFilter function.

These modifications improve the overall coherence of the codebase.

Also applies to: 75-79


83-85: Improved function naming.

The function has been renamed from makeTransactionSearchQuery to transactionsSearch, which follows a more concise naming convention. This change improves clarity and consistency with the other renamed functions in this file.


Line range hint 31-85: Summary of changes: Improved naming conventions and type safety.

The modifications in this file contribute to the PR's objective of simplifying transaction loading:

  1. Functions have been renamed to follow more concise naming conventions:

    • getAccountFilteraccountFilter
    • makeTransactionsQuerytransactions
    • makeTransactionSearchQuerytransactionsSearch
  2. Type safety has been enhanced for accountFilter and transactions functions by specifying more precise types for the accountId parameter.

  3. The changes are consistent throughout the file and maintain the existing functionality while improving code clarity and robustness.

These updates should make the code easier to understand and use correctly, aligning well with the goal of simplifying transaction handling in the application.

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

Line range hint 1-359: Summary of changes and potential impact

The changes in this file are part of a larger refactoring effort to centralize and standardize data hooks. The useSchedules hook import and usage have been updated, which should improve code consistency and maintainability. These modifications don't introduce any apparent issues and align well with the PR objectives of simplifying transaction loading.

However, it's important to ensure that:

  1. Other components using the useSchedules hook are updated accordingly.
  2. The useSchedules hook implementation in the new location matches the expected interface.
  3. These changes are tested thoroughly, especially in conjunction with the other changes mentioned in the PR description (e.g., updates to the mobile transactions page).

116-116: LGTM! Verify useSchedules hook implementation.

The simplified destructuring of the useSchedules hook return value looks good. This change suggests that the hook now returns an object with a schedules property directly, which is a cleaner approach.

To ensure consistency and correct implementation, let's verify the useSchedules hook:

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

12-12: LGTM! Consider verifying usage in other components.

The updated import path for useSchedules looks good. This change suggests a move towards a more centralized location for data hooks, which can improve code reusability and maintainability.

To ensure consistency across the codebase, let's verify if there are any other components still using the old import path:

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

253-253: Enhanced immutability for items array

The change from const items: SchedulesTableItem[] to const items: readonly SchedulesTableItem[] is consistent with the prop type change and further enforces immutability within the component. This is a good practice that can help prevent accidental mutations of the items array.

Let's verify that this change doesn't affect any operations on the items array within the component:

#!/bin/bash
# Search for any operations on the items array that might be affected by readonly
rg "items\." --type typescript --type tsx packages/desktop-client/src/components/schedules/SchedulesTable.tsx

33-33: Improved type safety with readonly modifier

The change from schedules: ScheduleEntity[] to schedules: readonly ScheduleEntity[] in the SchedulesTableProps type is a good practice. It ensures that the schedules prop cannot be accidentally mutated within the component, which can help prevent bugs and improve predictability.

To ensure this change doesn't break existing usage, let's verify how SchedulesTable is being used:

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

297-297: LGTM: Correct usage of the updated totalCount property.

The assertion has been updated to use paged.totalCount instead of the previous paged.getTotalCount() method. This change correctly reflects the new API for accessing the total count of paged results.


308-308: LGTM: Correct usage of the updated hasNext property.

The assertion has been updated to use paged.hasNext instead of the previous paged.isFinished() method. This change correctly reflects the new API for checking if there are more pages to fetch.


319-319: LGTM: Consistent usage of the hasNext property.

The assertion correctly uses the paged.hasNext property to check if there are more pages to fetch. This is consistent with the previous usage and accurately reflects the state of the paged query at this point in the test.


331-332: LGTM: Correct data validation and end-of-data check.

These assertions correctly validate the final state of the paged query:

  1. The paged.data is checked against the expected selected data.
  2. The paged.hasNext property is used to confirm that we've reached the end of the data set.

Both changes accurately reflect the new API and the expected behavior of the paged query.

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

21-25: Re-evaluate Suppression of react-hooks/exhaustive-deps Warning

The eslint-disable-next-line react-hooks/exhaustive-deps comment suppresses the linter warning for missing dependencies in useMemo. While you mention that changes to the function that creates the query aren't a concern, ensure that all variables used inside makeQuery are included in dependencies. This prevents potential stale closures if makeQuery relies on external variables.

To verify, check if makeQuery depends on any variables outside its scope that aren't listed in dependencies. If so, include them to ensure query updates correctly when those variables change.

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

22-22: Initialization of dispatch is correct

The dispatch function is correctly initialized using useDispatch(), adhering to Redux best practices.


25-30: Memoizing onEdit with appropriate dependencies

The onEdit callback is properly memoized using useCallback, with dispatch included in the dependency array.


32-34: Memoizing onAdd with appropriate dependencies

The onAdd callback is properly memoized using useCallback, with dispatch included in the dependency array.


36-38: Memoizing onDiscover with appropriate dependencies

The onDiscover callback is properly memoized using useCallback, with dispatch included in the dependency array.


66-66: Verify dependency array for onAction useCallback

The dependency array for onAction is currently empty. Ensure that all variables used within the callback that are subject to change are included in the dependency array to prevent stale references.

Run the following script to check for missing dependencies:

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

3-5: Updated 'ObjectExpression' Type Definition is Appropriate

The recursive definition of ObjectExpression allows for nested expressions, which is suitable for complex query building.


8-13: Enhancements to 'QueryState' Improve Flexibility

Introducing table and tableOptions properties and updating the types of expression arrays enhance the configurability of queries.


25-27: Constructor Initializes State with Proper Defaults

The constructor correctly merges the provided state with default values, ensuring all necessary properties are initialized.


84-88: 'calculate' Method Correctly Extends 'select'

Using select within calculate to set up a calculation query is a coherent and logical approach.


Line range hint 90-100: Consistent Handling of Expressions in 'groupBy' and 'orderBy'

The methods appropriately handle various input types for expressions, providing flexibility in query construction.


136-139: Verify the Behavior of the 'reset' Method

The reset method returns a new query for the same table using the q function. Confirm that this resets all state properties and does not carry over filters, selections, or other configurations unintentionally.

Would you like to ensure that reset fully reinitializes the query state? If necessary, we can write tests to verify this behavior.


145-148: Update 'getPrimaryOrderBy' Function Signature

The function signature now requires a Query instance and an optional ObjectExpression. This change enhances type safety and aligns with the updates to the Query class.

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

27-34: Ensure stability of getCategoryMonthFilter within useCallback

The baseTransactionsQuery function uses useCallback with [category, month] as dependencies. Ensure that getCategoryMonthFilter doesn't introduce any side effects or dependencies that could affect the memoization. If getCategoryMonthFilter is stable, this usage is appropriate.


45-45: Consistent default for dateFormat

The default date format is set to 'MM/dd/yyyy'. Ensure this default aligns with the application's overall date format settings to maintain consistency across the user interface.


48-65: Proper cleanup of event listeners in useEffect

In the useEffect, the return value of listen is used for cleanup, which is correct. This ensures that the event listener for 'sync-event' is removed when the component unmounts, preventing potential memory leaks.


68-77: Verify dependencies in useCallback and useDebounceCallback

In updateSearchQuery, the dependencies include updateTransactionsQuery, baseTransactionsQuery, and dateFormat. Confirm that these are necessary and that changes to them will correctly trigger updates. This ensures that the debounced callback functions as intended.


128-128: Ensure prop names match function names

In the TransactionListWithBalances component, the prop onOpenTransaction is passed. Verify that this prop matches the corrected function name, especially after fixing the typo. Inconsistencies could lead to runtime errors.

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

7-9: Imports are appropriate and necessary

The added imports (useRef, useMemo, PropsWithChildren, and various type imports) are correctly included to support the new functionality. They enhance type safety and are used appropriately in the codebase.

Also applies to: 16-22


90-120: Proper handling of component unmounting in useEffect

The implementation of the isUnmounted flag within the useEffect hook is correct. It prevents state updates after the component has unmounted, which is a good practice to avoid potential memory leaks or runtime errors.


158-178: defaultSchedulesQueryBuilder is correctly implemented

The defaultSchedulesQueryBuilder function enhances modularity by allowing dynamic query building based on accountId. The filtering logic appears sound, and the use of memoization will improve performance by preventing unnecessary computations.


29-53: ⚠️ Potential issue

Ensure loadStatuses handles subscriptions correctly

In the loadStatuses function, you initiate a live query but there is no explicit cleanup mechanism within loadStatuses itself for unsubscribing from this query when it's no longer needed.

Consider modifying loadStatuses to return the unsubscribe function from the liveQuery, so that you can properly unsubscribe when the component unmounts. Update the calling code to handle this unsubscribe:

 function loadStatuses(
   schedules: readonly ScheduleEntity[],
   onData: (data: ScheduleStatuses) => void,
   upcomingLength: number,
 ) {
-  return liveQuery<TransactionEntity>(
+  const unsubscribe = liveQuery<TransactionEntity>(
     getHasTransactionsQuery(schedules),
     data => {
       // ...
     },
   );
+  return unsubscribe;
 }

 // In useEffect:
 statusQueryRef.current = loadStatuses(
   schedules,
   (statuses: ScheduleStatuses) => {
     if (!isUnmounted) {
       setIsLoading(false);
       setData({ schedules, statuses });
     }
   },
   upcomingLengthNumber,
 );
+// Ensure unsubscribe happens on unmount

This ensures that all subscriptions are properly cleaned up, preventing potential memory leaks.


131-136: 🛠️ Refactor suggestion

Reconsider providing default values in SchedulesContext

By initializing SchedulesContext with default values, components consuming this context will receive these defaults even if SchedulesProvider is missing higher in the component tree. This could mask bugs where the provider is absent, leading to unexpected behavior that's hard to trace.

Consider keeping the initial context value as undefined to allow consumers to detect and handle the absence of the provider appropriately. To verify if any consumers check for undefined context values, you can run the following script:

This will help identify if any components rely on the context being undefined to function correctly.

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

298-321: Dependencies for onOpenTransaction's useCallback

The onOpenTransaction function depends on isPreviewId, dispatch, and navigate. While dispatch and navigate are included in the dependency array, isPreviewId is a stable import and doesn't need to be included. The dependencies appear correctly specified.


Line range hint 106-155: Proper use of useCallback for memoizing handlers

The callbacks onSave, onSaveNotes, onEditNotes, onCloseAccount, onReopenAccount, and onClick are appropriately wrapped with useCallback and include all necessary dependencies. This ensures that the functions are memoized correctly, preventing unnecessary re-renders.


256-271: Event listener cleanup in useEffect hook

The useEffect hook returns the listener cleanup function correctly, ensuring that the event listener is properly removed when the component unmounts or dependencies change.


224-226: Confirm queries.transactions supports new accountId values

The baseTransactionsQuery uses queries.transactions(accountId) where accountId may be undefined or one of the new string literals. Please verify that queries.transactions can handle these values appropriately.

#!/bin/bash
# Description: Check the implementation of `queries.transactions` for handling various `accountId` values.

# Test: Locate the `transactions` query builder function and review its parameter usage.
rg --type ts 'export function transactions\(' -A 10

217-222: Ensure accountId types are compatible throughout the codebase

The accountId prop now accepts additional string literals along with AccountEntity['id']. Verify that all functions and components receiving accountId can handle these new string values without issues.

#!/bin/bash
# Description: Find all usages of `accountId` and check for type compatibility.

# Test: Search for functions that use `accountId` and ensure they handle the new types.
rg --type ts 'function.*\(.*accountId' -A 5

59-62: Verify defaultSchedulesQueryBuilder handles all accountId cases

The accountId passed to defaultSchedulesQueryBuilder can be undefined or one of the string literals 'budgeted', 'offbudget', or 'uncategorized'. Please ensure that defaultSchedulesQueryBuilder correctly handles these values to prevent potential runtime errors.

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

241-245: Ensure fetchCount resolves before using this._totalCount

Since fetchCount() returns a promise, any logic depending on this._totalCount should ensure that the promise has resolved.

Please check that this._totalCount is not used before fetchCount() has completed. If necessary, adjust the code to wait for its resolution.


40-40: ⚠️ Potential issue

Clarify the type definition of _unsubscribeSyncEvent

Using void in a union type can be confusing. It's better to group the function type with parentheses to improve readability.

Apply this diff to fix the issue:

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

Likely invalid or redundant comment.

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

312-317: LGTM: Proper destructuring of useSchedules return values

The updated code correctly destructures schedules, statuses, and isLoading from useSchedules, enhancing code readability and maintainability.

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

246-246: Verify accountId handling in queries.transactions.

The accountId now accepts string values like 'budgeted', 'offbudget', and 'uncategorized', which are passed to queries.transactions(accountId). Please confirm that queries.transactions can handle these string literals without causing errors.

Also applies to: 488-488

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

770-777: LGTM

The implementation correctly handles the loading state when fetching schedules. Returning null during loading is appropriate and prevents potential rendering issues.

packages/desktop-client/e2e/accounts.test.js Show resolved Hide resolved
packages/loot-core/src/client/data-hooks/filters.ts Outdated Show resolved Hide resolved
packages/loot-core/src/client/query-hooks.ts Outdated Show resolved Hide resolved
.eslintrc.js Show resolved Hide resolved
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 (11)
packages/loot-core/src/client/query-hooks.ts (1)

28-44: LGTM: Enhanced effect logic with improved error handling

The updates to the effect logic are well-implemented and improve the hook's robustness:

  1. Error handling for null query is a good addition.
  2. Early return for null query prevents unnecessary processing.
  3. The isUnmounted flag helps prevent state updates after unmounting.
  4. Separate onData and onError callbacks in liveQuery improve separation of concerns.

These changes contribute to better error handling and prevent potential issues with state updates after unmounting.

Consider using a ref instead of a regular variable for isUnmounted to ensure its value is always up-to-date in the onData callback:

const isUnmountedRef = useRef(false);
useEffect(() => {
  // ...
  return () => {
    isUnmountedRef.current = true;
    // ...
  };
}, [query]);

This approach is more reliable in handling race conditions that might occur during cleanup.

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

25-30: LGTM: Optimized onEdit callback with Redux integration

The onEdit function has been optimized using useCallback, which is great for performance. The switch to using Redux dispatch for modal actions ensures consistency in state management.

Consider adding a type annotation for the id parameter to improve code readability:

const onEdit = useCallback(
  (id: ScheduleEntity['id']) => {
    dispatch(pushModal('schedule-edit', { id }));
  },
  [dispatch]
);

40-68: Approve with suggestions: Comprehensive onAction callback

The onAction function effectively centralizes schedule action logic, which is great for maintainability. The use of useCallback is consistent with other functions in the component.

Consider the following improvements:

  1. Add error handling for async operations:
try {
  // switch statement
} catch (error) {
  console.error('Error performing schedule action:', error);
  // Optionally, dispatch an error action or show a user-friendly message
}
  1. If the switch statement grows larger, consider refactoring it into a separate function or using an object literal for action mapping:
const actionHandlers = {
  'post-transaction': (id) => send('schedule/post-transaction', { id }),
  'skip': (id) => send('schedule/skip-next-date', { id }),
  // ... other actions
};

const onAction = useCallback(
  async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
    const handler = actionHandlers[name];
    if (handler) {
      await handler(id);
    } else {
      throw new Error(`Unknown action: ${name}`);
    }
  },
  []
);

This approach can make the code more maintainable as new actions are added.

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

47-63: Improved sync event handling with a minor optimization opportunity

The simplified useEffect for sync event handling aligns well with the new useTransactions hook usage. The additional conditions for reloading transactions enhance data consistency.

Consider this minor optimization:

- if (
-   tables.includes('transactions') ||
-   tables.includes('category_mapping') ||
-   tables.includes('payee_mapping')
- ) {
+ if (['transactions', 'category_mapping', 'payee_mapping'].some(table => tables.includes(table))) {

This change would slightly improve readability and performance.


65-86: LGTM: Improved search query update with a minor suggestion

The updateSearchQuery function has been successfully adapted to use the new updateTransactionsQuery from the useTransactions hook. The logic remains consistent with the previous implementation.

For improved consistency, consider this minor change:

- if (searchText === '') {
-   updateTransactionsQuery(() => baseTransactionsQuery().select('*'));
- } else if (searchText) {
+ if (searchText) {
    updateTransactionsQuery(currentQuery =>
      queries.transactionsSearch(currentQuery, searchText, dateFormat),
    );
+ } else {
+   updateTransactionsQuery(() => baseTransactionsQuery().select('*'));
  }

This change would make the logic more consistent and slightly more efficient.

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

Ensure upcomingLength is properly type-converted

The analysis confirms that upcomingLength is consistently used as a string without any type conversion. This may lead to type-related issues where a numerical value is expected.

  • Occurrences:
    • upcomingLength: string,
    • const [upcomingLength] = useSyncedPref('upcomingScheduledTransactionLength');
    • Usage within dependencies: [query, upcomingLength]

Recommendation:

  • Apply parseInt or another appropriate type conversion to upcomingLength where numerical operations are required.
  • Review all usages to ensure type consistency and prevent potential bugs.
🔗 Analysis chain

Line range hint 1-178: Overall excellent improvements to the scheduling system

The changes in this file significantly enhance the scheduling system:

  • Improved type safety throughout the file.
  • Better structure and error handling in the useSchedules hook.
  • More flexible and descriptive SchedulesProvider component.
  • Addition of the useful defaultSchedulesQueryBuilder function.
  • Enhanced context usage with default values.

These improvements collectively make the code more maintainable, type-safe, and easier to use and extend in the future.

One minor point to keep in mind:
The upcomingLength parameter in the loadStatuses function may still need type conversion, as mentioned in a previous review. Consider addressing this in a future update.

Great work on this refactoring!

To ensure that the upcomingLength issue is properly addressed, you can run the following script:

This script will help identify any instances where upcomingLength is used without being converted to a number, which could potentially lead to type-related issues.


To address the regex issue, please run the updated script below:


To resolve the issue, please run the updated script below:


Let's proceed by running the updated script below, which directly targets the file without specifying the --type:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential type mismatches with upcomingLength

# Test: Search for usages of upcomingLength that might need type conversion
rg --type typescript 'upcomingLength(?!.*parseInt)' packages/loot-core/src/client/data-hooks/schedules.tsx

Length of output: 354


Script:

#!/bin/bash
# Description: Check for potential type mismatches with upcomingLength using PCRE2

# Test: Search for usages of upcomingLength that might need type conversion
rg --pcre2 --type typescript 'upcomingLength(?!.*parseInt)' packages/loot-core/src/client/data-hooks/schedules.tsx

Length of output: 151


Script:

#!/bin/bash
# Description: Check for potential type mismatches with upcomingLength using PCRE2

# Test: Search for usages of upcomingLength that might need type conversion
rg --pcre2 --type tsx 'upcomingLength(?!.*parseInt)' packages/loot-core/src/client/data-hooks/schedules.tsx

Length of output: 137


Script:

#!/bin/bash
# Description: Check for potential type mismatches with upcomingLength using PCRE2 without specifying file type

# Test: Search for usages of upcomingLength that might need type conversion
rg --pcre2 'upcomingLength(?!.*parseInt)' packages/loot-core/src/client/data-hooks/schedules.tsx

Length of output: 289

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

32-89: Well-implemented useTransactions hook with room for minor improvement.

The useTransactions hook is well-structured and follows React best practices. It efficiently manages state, handles side effects, and implements pagination. The use of useRef for pagedQueryRef and optionsRef is a good approach to avoid unnecessary re-renders.

One minor suggestion:

Consider adding a catch block to the useEffect to handle any errors that might occur during the execution of pagedQuery. This would ensure that the loading state is properly reset even if an error occurs:

useEffect(() => {
  // ... existing code ...

  pagedQueryRef.current = pagedQuery<TransactionEntity>(query, {
    // ... existing options ...
  }).catch(error => {
    if (!isUnmounted) {
      setError(error);
      setIsLoading(false);
    }
  });

  // ... existing cleanup code ...
}, [query]);

This addition would improve error handling and ensure the loading state is always accurate.

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

10-45: LGTM: Enhanced type safety and flexibility in query functions

The updates to liveQuery and pagedQuery functions significantly improve type safety and provide more flexibility in passing options. The use of static methods runLiveQuery and runPagedQuery enhances code organization and reusability.

For consistency, consider using the same naming convention for the options parameter in both functions:

-  options = {},
+  options: LiveQueryOptions = {},

in the liveQuery function, similar to how it's done in the pagedQuery function.


60-238: LGTM: Improved encapsulation and error handling in LiveQuery class

The refactoring of the LiveQuery class significantly enhances encapsulation and provides better control over the internal state. The addition of getter and setter methods, along with new utility methods like addListener, onData, onError, and onUpdate, improves the class's flexibility and usability. The error handling in the fetchData method is now more robust.

Consider updating the type of _unsubscribeSyncEvent for better clarity:

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

This change makes it clear that the property holds either a function that returns void or null, rather than a function that returns void or null.

🧰 Tools
🪛 Biome

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)


Line range hint 247-426: LGTM: Enhanced paging functionality and type safety in PagedQuery class

The updates to the PagedQuery class significantly improve its paging functionality and type safety. The addition of new private properties and methods like fetchCount, refetchUpToRow, and fetchNext provide more control over the paging behavior. The override of the optimisticUpdate method to correctly update the total count is a good improvement.

Consider updating the fetchCount method to use async/await for consistency with other asynchronous methods in the class:

-  private fetchCount = () => {
-    return runQuery(this.query.calculate({ $count: '*' })).then(({ data }) => {
-      this._totalCount = data;
-    });
+  private fetchCount = async () => {
+    const { data } = await runQuery(this.query.calculate({ $count: '*' }));
+    this._totalCount = data;
+  };

This change makes the code more consistent and easier to read.

🧰 Tools
🪛 Biome

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

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

300-301: LGTM with suggestion: pagedQuery call updated in paged requests test.

The change correctly implements the new API structure for pagedQuery in the context of testing paged requests. However, consider including the onPageData callback in the object parameter for consistency with the test logic:

 const paged = pagedQuery(query, {
   onData: data => tracer.event('data', data),
+  onPageData: data => tracer.event('page-data', data),
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 13d8a9e and 31c80f9.

📒 Files selected for processing (12)
  • packages/desktop-client/src/components/accounts/Account.tsx (15 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (7 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (2 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 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.test.ts (17 hunks)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
  • packages/loot-core/src/client/query-hooks.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts
  • packages/loot-core/src/client/data-hooks/filters.ts
🧰 Additional context used
📓 Learnings (2)
packages/desktop-client/src/components/accounts/Account.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/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/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 (51)
packages/loot-core/src/client/query-hooks.ts (7)

7-11: LGTM: Well-defined type for query results

The new UseQueryResult type is well-structured and provides a comprehensive result for the useQuery hook. The use of ReadonlyArray for data is a good practice as it promotes immutability.


14-16: LGTM: Improved function signature

The updated useQuery function signature is an improvement:

  1. Renaming deps to dependencies is more descriptive.
  2. The return type UseQueryResult<Response> aligns with the new type definition.
  3. Allowing makeQuery to return null provides more flexibility in handling edge cases.

21-25: LGTM: Improved state management and memoization

The updates to state initialization and query memoization are well-implemented:

  1. The data state now correctly uses ReadonlyArray<Response> | null.
  2. isLoading is initialized based on the presence of a query, which is more accurate.
  3. The new error state allows for better error handling.
  4. The comment explaining the eslint disable for the memoization is a good practice.

These changes contribute to more robust and predictable behavior of the hook.


47-50: LGTM: Comprehensive cleanup logic

The cleanup logic is well-implemented:

  1. Setting isUnmounted to true prevents further state updates after unmounting.
  2. Unsubscribing from the live query prevents potential memory leaks.
  3. Setting live to null ensures it can't be accidentally used after cleanup.

This cleanup approach effectively prevents issues related to unmounted components and resource management.


55-57: LGTM: Comprehensive return object

The updated return statement now includes the error property, which aligns with the UseQueryResult type. This addition allows consumers of the hook to handle errors more effectively, improving the overall robustness of the code using this hook.


Line range hint 1-57: Overall, excellent improvements to the useQuery hook

The changes to this file significantly enhance the useQuery hook:

  1. Introduction of a well-defined UseQueryResult type.
  2. Improved error handling and state management.
  3. Better cleanup logic to prevent issues with unmounted components.
  4. More comprehensive return object including error information.

These improvements contribute to a more robust, type-safe, and easier-to-use query hook.


Line range hint 1-57: Verify removal of useLiveQuery

The AI-generated summary mentions the removal of the useLiveQuery function, but this change is not visible in the provided code snippet. Let's verify this change.

✅ Verification successful

Removal of useLiveQuery Confirmed

The useLiveQuery function has been successfully removed from the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the removal of useLiveQuery function

# Test: Search for useLiveQuery function definition
rg --type typescript 'export function useLiveQuery' packages/loot-core/src/client/query-hooks.ts

# If the function is indeed removed, the above command should return no results

Length of output: 133


Script:

#!/bin/bash
# Description: Verify the removal of useLiveQuery function by searching for its definition

# Test: Search for useLiveQuery function definition without using --type flag
rg 'export function useLiveQuery' packages/loot-core/src/client/query-hooks.ts

Length of output: 80


Script:

#!/bin/bash
# Description: Verify complete removal of useLiveQuery function across the codebase

# Test: Search for any occurrence of useLiveQuery in all files
rg 'useLiveQuery'

Length of output: 17

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

1-3: LGTM: Improved imports for optimization and state management

The addition of useCallback and useDispatch imports suggests a move towards optimized callback functions and centralized state management using Redux. These changes align well with React best practices for performance and maintainability.


22-22: LGTM: Redux dispatch integration

The use of useDispatch hook is a good practice for integrating Redux state management. This change provides a consistent way to dispatch actions throughout the component.


32-38: LGTM: Consistent implementation of modal action callbacks

The onAdd and onDiscover functions are well-implemented using useCallback. This approach ensures consistency with the onEdit function and optimizes performance. The functions are concise and follow the single responsibility principle.


70-73: LGTM: Improved loading state handling

The addition of the isLoading state from the useSchedules hook and the subsequent check for isSchedulesLoading are excellent improvements. This approach enhances the user experience by preventing the rendering of incomplete data and improves performance by avoiding unnecessary renders during the loading phase.


Line range hint 75-145: LGTM: Consistent UI updates

The changes to the component's JSX are minimal and appropriate. The addition of the onAction prop to the SchedulesTable component is consistent with the new onAction function, allowing for centralized handling of schedule actions. The overall structure of the UI remains intact, which is good for maintaining consistency in the user experience.

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

7-7: LGTM: New import for useTransactions hook

The import for the new useTransactions hook is correctly added and aligns with the PR objective of simplifying transaction loading.


27-33: LGTM: Improved function naming and implementation

The renaming of makeRootQuery to baseTransactionsQuery enhances code readability by clearly indicating the function's purpose. The implementation as a callback with correct dependencies is maintained.


35-43: Great job implementing useTransactions hook

The use of the useTransactions hook significantly simplifies transaction management by centralizing state and operations. This aligns perfectly with the PR objective and potentially reduces code complexity. The queryBuilder option ensures consistency with the previous implementation.


88-96: LGTM: Fixed typo in function name

The function has been correctly renamed from onOpenTranasction to onOpenTransaction, addressing the typo mentioned in the previous review. The implementation remains consistent and correct.


128-129: LGTM: Consistent updates in JSX

The changes in the JSX return statement are consistent with the new hook usage. The onLoadMore prop now correctly uses loadMoreTransactions from the useTransactions hook, and the onOpenTransaction prop name has been fixed.


Line range hint 1-143: Overall excellent refactoring with the new useTransactions hook

The changes in this file successfully implement the new useTransactions hook, significantly simplifying the management of transactions in the CategoryTransactions component. This refactoring aligns perfectly with the PR objectives and improves code maintainability.

Key improvements:

  1. Centralized transaction state management
  2. Simplified data fetching and updating logic
  3. Improved consistency in function naming and usage

The minor suggestions provided in the review comments could further enhance the code quality, but the overall implementation is solid and achieves the intended goals.

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

7-9: LGTM: Import statements updated appropriately

The added imports for useRef, useMemo, and PropsWithChildren are consistent with the changes made in the rest of the file, supporting the updated implementation of the useSchedules hook and the SchedulesProvider component.


16-22: Excellent: Enhanced type safety and query handling

The additional type imports (TransactionEntity, ScheduleEntity, AccountEntity) and the LiveQuery type significantly improve type safety throughout the file. The accountFilter import suggests enhanced filtering capabilities, which aligns well with the overall improvements to the scheduling logic.


29-53: Great improvements to loadStatuses, with a minor consideration

The updates to loadStatuses are excellent:

  • Using a Map for schedule statuses likely improves lookup performance.
  • The addition of the onError callback enhances error handling.
  • The function signature is now more explicit about its parameters and return type.

However, there's one point to consider:

The upcomingLength parameter is passed directly to getStatus without type conversion. As per a previous review comment, this might need to be converted to a number before use.


57-71: Excellent type restructuring

The changes to the type definitions significantly improve the clarity and flexibility of the code:

  • The new UseSchedulesProps type, which includes an options object with an isDisabled flag, allows for more granular control over the scheduling logic.
  • The separation of ScheduleData and UseSchedulesResult types provides a clearer structure for the data returned by the useSchedules hook.
  • The overall type structure is more organized and easier to understand.

These changes will likely make the code more maintainable and less prone to type-related errors.


73-126: Excellent refactoring of the useSchedules hook

The changes to the useSchedules hook significantly improve its structure and functionality:

  • The use of useState for isLoading, error, and data provides clear and separate state management.
  • Utilizing useRef for scheduleQueryRef and statusQueryRef allows for proper cleanup of live queries in the effect.
  • The memoized query with useMemo prevents unnecessary re-computations.
  • The effect now handles both schedule and status queries, ensuring proper data loading and cleanup.
  • Error handling is improved with the separate error state.

These changes make the hook more robust, easier to understand, and less prone to bugs related to stale data or memory leaks.


131-136: Good addition of default values to SchedulesContext

Providing default values for the SchedulesContext is a good practice:

  • It ensures that consumers of the context always have a consistent structure to work with, even if the provider is not present.
  • This change improves the robustness of the code using this context and helps prevent potential runtime errors.
  • It also serves as clear documentation of the expected shape of the context value.

This small change can significantly improve the reliability of components consuming this context.


Line range hint 138-153: Well-structured SchedulesProvider component

The changes to the SchedulesProvider component improve its functionality and clarity:

  • Renaming the prop from transform to queryBuilder is more descriptive and aligns better with its purpose.
  • Utilizing the useSchedules hook within the provider centralizes the data fetching logic, promoting better code organization.
  • This structure allows for easier reuse of the scheduling logic across the application.

These changes make the component more flexible and easier to use, while also improving the overall architecture of the scheduling system.


158-178: Excellent addition of defaultSchedulesQueryBuilder

The new defaultSchedulesQueryBuilder function is a valuable addition:

  • It centralizes the logic for building default schedule queries, promoting consistency across the application.
  • The filtering logic is comprehensive, covering various account scenarios (closed accounts, uncategorized, etc.).
  • Utilizing the accountFilter function demonstrates good code reuse and maintainability.
  • The function's structure allows for easy customization of queries while providing sensible defaults.

This addition will likely simplify query creation in other parts of the application and make it easier to maintain consistent querying logic for schedules.

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

91-101: Well-defined types for usePreviewTransactions.

The type definitions for UsePreviewTransactionsProps and UsePreviewTransactionsResult are clear, concise, and appropriate for the hook's functionality. The use of ReadonlyArray for the data in the result type is a good practice for enforcing immutability.


184-190: Well-implemented isForPreview helper function.

The isForPreview function is concise and correctly implements the logic to determine if a schedule should be previewed. It checks both the completion status and the relevant statuses as required.


1-190: Overall, excellent implementation of transaction-related hooks.

This file demonstrates a high-quality implementation of useTransactions and usePreviewTransactions hooks. The code is well-structured, follows React best practices, and includes appropriate error handling and state management. The type definitions are clear and promote type safety throughout the codebase.

A few minor optimizations were suggested, including:

  1. Optimizing the default query in useTransactions.
  2. Adding additional error handling in the useTransactions effect.
  3. Memoizing the baseTransactions array in usePreviewTransactions.

These suggestions, if implemented, would further improve the performance and robustness of the hooks. Great job on creating maintainable and efficient custom hooks for managing transaction data!

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

4-4: LGTM: Improved type safety and readability

The new import for getPrimaryOrderBy and Query type, along with the added type definitions for Data, Listener, LiveQueryOptions, and PagedQueryOptions, significantly enhance type safety and code readability. The use of ReadonlyArray for the Data type is a good practice for ensuring immutability.

Also applies to: 48-57, 242-244


Line range hint 1-426: LGTM: Excellent refactoring for improved type safety and maintainability

The overall structure and changes in this file demonstrate a significant improvement in type safety, encapsulation, and maintainability. The consistent use of TypeScript features, such as generics and explicit typing, throughout the file enhances code quality and reduces the potential for runtime errors.

The refactoring of both LiveQuery and PagedQuery classes, along with the updates to the liveQuery and pagedQuery functions, shows a thoughtful approach to improving the codebase. These changes will likely make the code easier to understand, maintain, and extend in the future.

🧰 Tools
🪛 Biome

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

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

140-140: LGTM: Updated pagedQuery call to use new object parameter structure.

The change correctly implements the new API structure for pagedQuery, improving clarity by explicitly naming the onData callback as a property of the object parameter.


156-159: LGTM: Updated pagedQuery call with new object parameter structure including options.

The change correctly implements the new API structure for pagedQuery, separating the onData callback and options as properties of the object parameter. The onlySync option is correctly included in the options property.


174-177: LGTM: Consistent update to pagedQuery call structure.

The change correctly implements the new API structure for pagedQuery, maintaining consistency with previous updates and preserving the existing test logic.


200-202: LGTM: pagedQuery call updated to new structure in cancellation test.

The change correctly implements the new API structure for pagedQuery in the context of testing request cancellation, maintaining consistency with previous updates and preserving the existing test logic.


230-233: LGTM: pagedQuery call updated in server push handling test.

The change correctly implements the new API structure for pagedQuery in the context of testing server push handling, maintaining consistency with previous updates and preserving the existing test logic.


254-257: LGTM: pagedQuery call updated in multiple server pushes test.

The change correctly implements the new API structure for pagedQuery in the context of testing multiple server pushes, maintaining consistency with previous updates and preserving the existing test logic.


279-281: LGTM: pagedQuery call updated in unsubscribe test.

The change correctly implements the new API structure for pagedQuery in the context of testing unsubscription, including only the necessary onData callback property and maintaining the existing test logic.


313-313: LGTM: Updated pagination properties in paged requests test.

The changes correctly implement the new API structure for pagination properties:

  • getTotalCount() method has been replaced with totalCount property.
  • isFinished() method has been replaced with hasNext property.

These updates improve readability and align with common naming conventions while maintaining the existing test logic.

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


361-363: LGTM: pagedQuery call updated in custom page count test.

The change correctly implements the new API structure for pagedQuery in the context of testing custom page counts. The pageCount option is appropriately nested within the options property, improving the organization of the parameters while maintaining the existing test logic.


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

The changes correctly implement the new API structure for pagedQuery across various test scenarios, including:

  • Concurrent fetch tests
  • Refetching tests
  • Fetching up to a specific row

All updates consistently use the new object parameter structure, appropriately including onData, onPageData (where applicable), and options properties. The pageCount option is correctly nested within the options property where needed. These changes improve the clarity and organization of the pagedQuery calls while maintaining the existing test logic for each scenario.

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


Line range hint 1-524: Overall assessment: Consistent and well-implemented updates to pagedQuery calls.

The changes in this file successfully update all pagedQuery function calls to use the new object parameter structure. These updates:

  1. Improve code clarity and organization
  2. Maintain existing test logic while adapting to the new API
  3. Align with the PR objectives of simplifying transaction loading

The consistent implementation across various test scenarios ensures that the new pagedQuery structure is thoroughly tested. Great job on maintaining the integrity of the test suite while refactoring the API!

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

22-22: Update import statements to improve code organization

The import statements have been updated to include new functions and types. This change improves code organization and type safety.

Also applies to: 25-29


246-246: Improved type definition for accountId

The accountId property in AccountInternalProps now has a more specific type definition, including string literals for special account types. This change enhances type safety and makes the code more self-documenting.


320-320: Enhanced type safety for paged property

The paged property in the AccountInternal class now uses the more specific PagedQuery<TransactionEntity> type. This change improves type safety and makes the code more robust.


Line range hint 505-553: Enhanced query handling with improved type safety and async/await syntax

The updateQuery method has been updated with several improvements:

  1. The pagedQuery function now uses type parameters, enhancing type safety.
  2. The onData callback has been refactored to use async/await syntax, improving readability and error handling.
  3. The use of PagedQuery<TransactionEntity> ensures consistent typing throughout the query handling process.

These changes contribute to better code quality and maintainability.


651-663: Simplified and optimized transaction update logic

The onTransactionsChange method has been refactored to use the optimisticUpdate function from the paged object. This change offers several benefits:

  1. Simplified logic for updating the transaction list.
  2. Potential performance improvement through optimistic updates.
  3. More concise and readable code.

These improvements contribute to better maintainability and potentially faster UI updates.


1880-1883: Improved schedule handling with memoized query builder

The Account function component has been updated with the following improvements:

  1. A new useMemo hook is used to create the schedulesQueryBuilder, which can optimize performance by memoizing the function.
  2. The main component tree is now wrapped with a SchedulesProvider, suggesting better management of schedules throughout the component.

These changes likely lead to improved performance and more efficient schedule handling.

Also applies to: 1886-1886


485-488: Improved method naming and simplified query logic

The makeRootQuery method has been renamed to makeRootTransactionsQuery, which better describes its purpose. The implementation has been simplified, potentially due to changes in the queries.transactions function.

To ensure the query logic is still correct, please run the following script:

✅ Verification successful

Retrying verification with corrected file type specification

The previous script failed because typescript is not a recognized file type in ripgrep. Please run the updated script below to verify the implementation of the queries.transactions function:


Verified renaming and simplification of makeRootTransactionsQuery

The queries.transactions function is correctly implemented in packages/loot-core/src/client/queries.ts. The renaming from makeRootQuery to makeRootTransactionsQuery and the simplified implementation in Account.tsx effectively enhance clarity and maintain functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the implementation of queries.transactions function
rg --type typescript 'export function transactions' -A 10

Length of output: 94


Script:

#!/bin/bash
# Verify the implementation of queries.transactions function in TypeScript files
rg 'export function transactions' --glob '*.ts' -A 10

Length of output: 1601

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

301-304: Update to liveQuery usage is correct and aligns with the API changes.

The modification to pass an object with the onData callback to liveQuery ensures proper handling of data updates according to the updated API.


344-347: Consistent restructuring of liveQuery calls is appropriate.

The change to use an object with the onData property in the liveQuery function promotes consistency and aligns with the updated API usage.

packages/loot-core/src/client/data-hooks/transactions.ts Outdated Show resolved Hide resolved
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 (16)
packages/loot-core/src/shared/query.ts (3)

49-57: Unfilter method improved, but type annotation can be simplified further.

The addition of functionality to clear all filters when no arguments are passed is a useful improvement. However, as suggested in a previous review, the type annotation for the exprs parameter can be simplified.

Consider simplifying the type annotation as follows:

-unfilter(exprs?: Array<keyof ObjectExpression>) {
+unfilter(exprs?: string[]) {

This change maintains the same functionality while improving readability.


Line range hint 90-110: GroupBy and OrderBy methods updated for increased flexibility.

Both groupBy and orderBy methods now accept more flexible input types, allowing for more complex grouping and ordering expressions. This change enhances the versatility of these methods.

Given the similarity in implementation between groupBy and orderBy, consider refactoring to reduce code duplication. You could create a helper function to handle the common logic:

private updateExpressions(
  type: 'groupExpressions' | 'orderExpressions',
  exprs: ObjectExpression | string | Array<ObjectExpression | string>
) {
  if (!Array.isArray(exprs)) {
    exprs = [exprs];
  }
  return new Query({
    ...this.state,
    [type]: [...this.state[type], ...exprs],
  });
}

groupBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
  return this.updateExpressions('groupExpressions', exprs);
}

orderBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
  return this.updateExpressions('orderExpressions', exprs);
}

This refactoring would reduce code duplication and make future updates easier to maintain.


Line range hint 112-138: Methods updated for improved type safety and functionality.

The limit and offset methods now have explicit number type annotations, improving type safety. The options method accepts a more flexible Record<string, unknown> type, allowing for various table options. The new reset method provides a convenient way to reset the query to its initial state.

Consider adding a comment to the reset method to clarify its purpose:

/**
 * Resets the query to its initial state, maintaining only the table.
 * @returns A new Query instance with only the table set.
 */
reset() {
  return q(this.state.table);
}

This comment would help developers understand the method's functionality at a glance.

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

73-126: LGTM with a suggestion: Enhanced useSchedules hook implementation.

The refactored useSchedules hook significantly improves performance and error handling. The use of useRef for live queries and useMemo for query memoization are excellent optimizations.

Suggestion for improved readability:
Consider extracting the query setup logic into a separate function within the effect. This would make the effect body more concise and easier to understand at a glance.

Example:

useEffect(() => {
  let isUnmounted = false;

  const setupQueries = () => {
    scheduleQueryRef.current = liveQuery<ScheduleEntity>(query, {
      // ... existing logic
    });
  };

  setIsLoading(query !== null);
  setupQueries();

  return () => {
    isUnmounted = true;
    scheduleQueryRef.current?.unsubscribe();
    statusQueryRef.current?.unsubscribe();
  };
}, [query, upcomingLength]);

This change is optional and aimed at enhancing code organization.


158-178: LGTM with a suggestion: Well-structured defaultSchedulesQueryBuilder function.

The new defaultSchedulesQueryBuilder function provides a flexible and reusable way to construct queries for schedules based on different account types. The handling of special cases and the use of the accountFilter function are particularly noteworthy.

Suggestion for improved clarity:
Consider adding a brief comment explaining the purpose of the filterByPayee condition. This would help future developers understand why both account and payee filters are necessary.

Example:

// Filter by account directly or by payee's transfer account
const filterByAccount = accountFilter(accountId, '_account');
const filterByPayee = accountFilter(accountId, '_payee.transfer_acct');

This minor addition would enhance the function's self-documentation.

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

10-45: Enhanced type safety and improved function signatures

The updates to liveQuery and pagedQuery functions significantly improve type safety and make the function signatures more explicit. The use of static methods runLiveQuery and runPagedQuery for creating and running queries is a good organizational choice.

However, for consistency, consider using the same naming convention for the options parameter in both functions.

Apply this minor change for consistency:

-  options?: LiveQueryOptions;
+  options?: LiveQueryOptions;
 },
): LiveQuery<TResponse> {
  return LiveQuery.runLiveQuery<TResponse>(query, onData, onError, options);
}

export function pagedQuery<TResponse = unknown>(
  query: Query,
  {
    onData,
    onError,
    onPageData,
-    options = {},
+    options = {},
  }: {
    onData?: Listener<TResponse>;
    onError?: (error: Error) => void;
    onPageData?: (data: Data<TResponse>) => void;
-    options?: PagedQueryOptions;
+    options?: PagedQueryOptions;
  },
): PagedQuery<TResponse> {

60-238: Improved encapsulation and error handling in LiveQuery class

The refactoring of the LiveQuery class significantly improves encapsulation through the use of private properties and controlled access via getter and setter methods. The addition of error handling in the fetchData method enhances the robustness of the code.

To further improve error handling, consider adding more specific error types and providing more detailed error information.

Consider enhancing the error handling like this:

 } catch (e) {
-  console.log('Error fetching data', e);
-  this.onError(e);
+  console.error('Error fetching data:', e);
+  this.onError(e instanceof Error ? e : new Error('Unknown error occurred while fetching data'));
 }
🧰 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: Enhanced PagedQuery class with improved type safety and pagination handling

The refactoring of the PagedQuery class, including its extension of LiveQuery<TResponse> and the addition of private properties for pagination state management, significantly improves type safety and encapsulation. The update to the optimisticUpdate method to adjust the total count is a valuable improvement.

However, there's an issue with the fetchCount method call in the run method.

The fetchCount method is called without awaiting its completion in the run method. This may lead to race conditions or incorrect total count values. Apply this fix:

 run = () => {
   this.subscribe();

   this._fetchDataPromise = this.fetchData(async () => {
     this._hasReachedEnd = false;

     // Also fetch the total count
-    this.fetchCount();
+    await this.fetchCount();

     // If data is null, we haven't fetched anything yet so just
     // fetch the first page
     return runQuery(
       this.query.limit(
         this.data == null
           ? this._pageCount
           : Math.max(this.data.length, this._pageCount),
       ),
     );
   });

   return this._fetchDataPromise;
 };
🧰 Tools
🪛 Biome

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

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

378-380: LGTM: Comprehensive and consistent application of new pagedQuery structure

The updates to all pagedQuery calls in this file demonstrate a thorough and consistent application of the new API structure. This systematic change enhances code readability and maintainability throughout the test suite, aligning perfectly with the PR's objectives.

Given the repetitive nature of these changes, consider exploring opportunities for further refactoring, such as creating a helper function for pagedQuery calls with common options, to reduce duplication and improve maintainability.

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

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

312-329: Improved data handling, but consider enhancing empty state feedback.

The refactoring of the ScheduleDescription component to use destructured values from the useSchedules hook improves code clarity and maintainability. The explicit handling of loading state and schedules is a positive change.

However, there's an opportunity to enhance user feedback when no schedules are found.

Consider updating the empty state rendering to provide more informative feedback to the user:

 if (schedules.length === 0) {
-  return <View style={{ flex: 1 }}>{id}</View>;
+  return <View style={{ flex: 1 }}>No schedule found for ID: {id}</View>;
 }

This change would provide clearer information to the user about why no data is being displayed.


Line range hint 462-472: New 'link-schedule' operation added, consider documentation and testing.

The addition of the 'link-schedule' operation enhances the component's functionality by allowing users to link schedules within actions. This is a valuable feature that expands the capabilities of the rule editor.

Consider adding a brief comment explaining the purpose and functionality of the 'link-schedule' operation:

 ) : op === 'link-schedule' ? (
+  // Allows users to link a schedule to the current rule
   <>
     <View
       style={{
         padding: '5px 10px',
         color: theme.pageTextPositive,
       }}
     >
       {friendlyOp(op)}
     </View>
     <ScheduleDescription id={value || null} />
   </>
 ) : op === 'prepend-notes' || op === 'append-notes' ? (

Would you like me to generate unit tests for this new 'link-schedule' feature to ensure its proper functionality?


Line range hint 720-724: Enhanced functionality with split actions, consider documentation and refactoring.

The addition of split actions significantly improves the flexibility of the rule system. The new state structure and functions for handling split actions are well-implemented and provide a powerful feature set.

To improve code maintainability and readability, consider the following suggestions:

  1. Add comments to explain the purpose and functionality of new split-related functions:
+// Removes a split at the specified index and adjusts the remaining splits
 function onRemoveSplit(splitIndexToRemove) {
   // ... existing implementation ...
 }

+// Adds a new action to a specific split after the given index
 function addActionToSplitAfterIndex(splitIndex, actionIndex) {
   // ... existing implementation ...
 }
  1. Consider extracting some of the complex logic into custom hooks or utility functions. For example, you could create a custom hook for managing split actions:
function useSplitActions(defaultRule) {
  const [actionSplits, setActionSplits] = useState(() => {
    // ... initialization logic ...
  });

  const addActionToSplit = useCallback((splitIndex, actionIndex) => {
    // ... implementation ...
  }, []);

  const removeSplit = useCallback((splitIndexToRemove) => {
    // ... implementation ...
  }, []);

  return {
    actionSplits,
    addActionToSplit,
    removeSplit,
    // ... other relevant functions ...
  };
}

This refactoring would help separate concerns and make the main component easier to understand and maintain.

Would you like me to provide a more detailed example of how to refactor this component using custom hooks?

Also applies to: 820-850


Line range hint 1-1037: Overall improvement with new features, consider documentation and maintainability.

The changes to this file significantly enhance the functionality of the rule editor by introducing features like schedule linking and split actions. These additions provide users with more powerful and flexible rule creation capabilities.

To further improve the codebase:

  1. Consider adding more comprehensive documentation, especially for complex logic and new features. This will help future developers understand the system more easily.

  2. As the component grows in complexity, it might be beneficial to split it into smaller, more focused components. This could improve maintainability and make testing easier.

  3. Implement comprehensive unit and integration tests for the new features to ensure their reliability and catch potential regressions.

  4. Consider implementing a state management solution (e.g., Redux, MobX, or Recoil) if the application's state becomes too complex to manage with React's built-in state management.

These suggestions aim to ensure that as the application grows, it remains maintainable, testable, and easy to understand for all developers working on the project.

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

770-777: Improved asynchronous data handling in PayeeIcons component.

The changes to the PayeeIcons component enhance its ability to handle asynchronous schedule data. The use of useCachedSchedules and the addition of a loading state check are good improvements.

Consider adding a loading indicator or placeholder for a smoother user experience:

  if (isLoading) {
-   return null;
+   return <LoadingSpinner size="small" />;
  }

This would provide visual feedback to the user while the schedules are being loaded.


Line range hint 1-1530: Consider future refactoring for improved maintainability.

While the current changes are focused and beneficial, it's worth noting that this file is quite large and complex. In the future, consider breaking this component down into smaller, more manageable pieces. This could improve code readability, maintainability, and potentially performance through more granular rendering optimizations.

Some suggestions for future refactoring:

  1. Extract smaller components for reusable parts like individual table cells.
  2. Separate utility functions into a separate file.
  3. Consider using custom hooks to encapsulate complex logic, especially around data fetching and state management.
  4. Implement code-splitting if this component is causing performance issues due to its size.
packages/desktop-client/src/components/accounts/Account.tsx (1)

Line range hint 479-485: Check if filterConditions has elements before applying filters

In fetchTransactions, the condition if (filterConditions) will be true even if filterConditions is an empty array, leading to unnecessary calls to applyFilters. Consider changing the condition to check the length of filterConditions.

Apply this diff:

-    if (filterConditions) this.applyFilters(filterConditions);
+    if (filterConditions && filterConditions.length > 0) this.applyFilters(filterConditions);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 31c80f9 and 9560378.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
📒 Files selected for processing (35)
  • .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/Account.tsx (15 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (7 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/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/transactions/TransactionsTable.jsx (1 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/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)
💤 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 (19)
  • 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/Balance.jsx
  • packages/desktop-client/src/components/mobile/accounts/Accounts.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/SchedulesTable.tsx
  • 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/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
🧰 Additional context used
📓 Learnings (4)
.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/accounts/Account.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/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/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/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 (61)
packages/loot-core/src/client/query-hooks.ts (7)

7-11: LGTM: Well-defined type for query result

The new UseQueryResult type is well-structured and provides a clear interface for the useQuery hook's return value. The use of ReadonlyArray for the data property is a good practice for ensuring immutability.


14-16: LGTM: Improved function signature

The updated useQuery function signature is more descriptive and flexible:

  • Renaming deps to dependencies improves clarity.
  • The return type now accurately reflects the structure defined by UseQueryResult.
  • Allowing makeQuery to return null provides more flexibility in usage.

17-21: LGTM: Appropriate query memoization

The memoization of the query is well-implemented:

  • The comment clearly explains the rationale behind ignoring changes to the function that creates the query.
  • Disabling the ESLint rule is appropriate in this case, given the explanation.

23-25: LGTM: Improved state initialization

The state initialization has been enhanced:

  • data state now uses ReadonlyArray<Response>, consistent with the type definition.
  • isLoading is intelligently initialized based on the existence of query.
  • The new error state allows for proper error handling.

27-50: LGTM: Enhanced effect hook with improved error handling

The effect hook has been significantly improved:

  • Error handling for null query enhances robustness.
  • The isUnmounted flag effectively prevents updates after component unmounting, addressing a concern raised in a previous review.
  • Proper use of onData and onError callbacks in liveQuery.
  • The cleanup function is correctly implemented.

These changes address the "Ensure Safe State Updates After Unmounting" issue mentioned in a previous review comment.


52-57: LGTM: Consistent return object

The return object now correctly includes the error property, making it consistent with the UseQueryResult type. This allows consumers of the hook to handle errors more effectively.


Line range hint 1-57: Overall: Excellent improvements to the query hooks

The changes to this file significantly enhance the useQuery hook:

  1. Introduction of a clear UseQueryResult type.
  2. Improved function signature with better naming and flexibility.
  3. Enhanced state management and error handling.
  4. Proper handling of component unmounting.

These improvements make the hook more robust, type-safe, and easier to use. The removal of useLiveQuery (mentioned in the AI summary but not visible in the provided code) suggests a move towards a more unified API.

Great work on refactoring and improving this crucial part of the application!

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

1-3: LGTM: Improved imports for better React practices

The addition of useCallback, useState, and useDispatch imports indicates a shift towards more modern React practices. This change can lead to better performance and cleaner code structure.


22-24: LGTM: Improved state management

The use of useDispatch for Redux actions and useState for local filter state demonstrates good practices in state management. This approach provides more granular control over actions and keeps UI-specific state local to the component.


25-30: LGTM: Optimized onEdit function

The onEdit function has been optimized using useCallback, which can improve performance by memoizing the function. The dependency array is correctly set to [dispatch], ensuring the function is only recreated when dispatch changes.


32-38: LGTM: Consistent optimization of modal actions

The onAdd and onDiscover functions are consistently implemented using useCallback, aligning with the onEdit function. This approach ensures optimal performance and consistent code style across modal-triggering functions.


70-73: LGTM: Improved data fetching and loading handling

The use of the useSchedules hook for fetching schedules and managing the loading state is a good practice. The null check for the loading state prevents rendering when data is not ready, which can help avoid potential rendering errors.


Line range hint 75-145: LGTM: Enhanced SchedulesTable functionality

The addition of the onAction prop to the SchedulesTable component allows for more direct handling of schedule actions from within the table. This change improves the component's functionality while maintaining a clean and well-organized structure.


40-68: ⚠️ Potential issue

Add error handling and verify dependency array

The onAction function comprehensively handles various schedule actions. However, there are two areas for improvement:

  1. Error Handling: Add try-catch blocks to handle potential errors in the async operations.
  2. Dependency Array: Verify if the empty dependency array is intentional. If send is stable and doesn't change, it's fine. Otherwise, consider adding it to the dependency array.

Here's a suggested implementation with error handling:

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) {
      console.error(`Error performing action "${name}" for schedule ${id}:`, error);
      // Consider adding user-facing error handling here
    }
  },
  [/* Add dependencies if necessary */],
);

To verify the stability of send, run the following script:

✅ Verification successful

Add Error Handling to onAction Function

The onAction function effectively manages various schedule actions. However, to enhance reliability and maintainability, consider the following improvements:

  1. Error Handling: Incorporate try-catch blocks to manage potential errors during async operations.

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) {
      console.error(`Error performing action "${name}" for schedule ${id}:`, error);
      // Consider adding user-facing error handling here
    }
  },
  [],
);
  1. Dependency Array: Verified that send is imported from a stable location and does not require inclusion in the dependency array.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if 'send' is imported from a stable location and if it's likely to change.

# Search for 'send' import
rg --type typescript "import.*send.*from" packages/desktop-client/src

Length of output: 106


Script:

#!/bin/bash
# Description: Check if 'send' is imported from a stable location in TypeScript files.

# Search for 'send' import in .ts and .tsx files
rg "import.*send.*from" --glob "*.ts" --glob "*.tsx" packages/desktop-client/src

Length of output: 6104

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

1-13: Improved type definitions enhance type safety and clarity.

The changes to the QueryState type and the introduction of the ObjectExpression type significantly improve the overall type safety and clarity of the query structure. The mandatory table property in QueryState ensures that every query is associated with a specific table, which is a good practice for maintaining data integrity.


Line range hint 25-41: Constructor changes improve Query initialization.

The updated constructor now requires a table property, ensuring that every Query instance is associated with a specific table. This change, along with the use of default values for other properties, improves the robustness of the Query class and simplifies its initialization.


42-48: Filter method updated to accept more complex expressions.

The filter method now accepts an ObjectExpression parameter, allowing for more complex filter expressions while maintaining a simple and clear implementation.


67-74: Select method signature provides improved flexibility.

The updated select method signature now allows for a wider range of input types, increasing the flexibility of the method. While a previous review suggested simplifying the signature, the current implementation accurately represents the possible inputs and maintains strong type safety.

The complexity of the signature is justified by the improved flexibility and type safety it provides. No further changes are necessary at this time.


84-89: Calculate method updated to accept more flexible expressions.

The calculate method now accepts either an ObjectExpression or a string, allowing for more flexible calculation expressions. This change enhances the versatility of the method while maintaining a simple and clear implementation.


145-148: getPrimaryOrderBy function signature improved for better type safety and integration.

The getPrimaryOrderBy function now accepts a Query object as its first parameter and types the defaultOrderBy parameter as ObjectExpression | null. These changes enhance type safety and improve the function's integration with the Query class.


166-168: q function updated for improved type consistency.

The q function now explicitly types its table parameter using QueryState['table']. This change ensures type consistency between the q function and the QueryState type, improving type safety and code maintainability.


Line range hint 1-168: Overall assessment: Significant improvements in type safety and functionality.

This update to the query handling system introduces several important improvements:

  1. Enhanced type safety throughout the file.
  2. More flexible input types for various methods, allowing for more complex queries.
  3. Better consistency in method signatures and parameter types.
  4. Introduction of useful new functionality, such as the reset method.

The changes align well with the PR objectives of simplifying transaction loading and previewing. The improved query system should provide a solid foundation for implementing the useTransactions hook mentioned in the PR summary.

While some minor suggestions for further improvements have been made, the overall quality of the changes is high and significantly enhances the query handling capabilities of the application.

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

7-9: LGTM: Import additions are appropriate.

The added imports (useRef, useMemo, and PropsWithChildren) align well with the changes in the component logic, enhancing type safety and performance optimization.


16-22: LGTM: Import additions enhance type safety and functionality.

The new imports for entity types (TransactionEntity, ScheduleEntity, AccountEntity) and utility functions (accountFilter, LiveQuery, liveQuery) are appropriate for the refactored code, improving type safety and providing necessary functionality.


57-71: LGTM: Improved type definitions enhance code clarity and maintainability.

The new type definitions (UseSchedulesProps, ScheduleData, and updated UseSchedulesResult) provide a clearer structure for the hook's inputs and outputs. These changes align well with the hook's new functionality and improve overall code maintainability.


131-136: LGTM: Improved SchedulesContext with default values.

Providing default values for the SchedulesContext is an excellent practice. It ensures that components consuming this context will always have a consistent structure, even when rendered outside of a provider. This change enhances the robustness of the context usage throughout the application.


Line range hint 138-153: LGTM: Streamlined SchedulesProvider component.

The updates to the SchedulesProvider component, including the addition of the queryBuilder prop and the simplified implementation, align well with the changes in the useSchedules hook. These modifications enhance the component's flexibility and improve its overall readability and maintainability.


Line range hint 1-178: Overall assessment: Excellent refactoring that aligns with PR objectives.

The changes in this file significantly improve the management of schedules and their statuses. The introduction of the useSchedules hook, along with the supporting types and components, provides a more efficient and flexible solution for loading and previewing transactions.

Key improvements:

  1. Enhanced type safety through better type definitions and imports.
  2. Improved performance with the use of useRef for live queries and useMemo for query memoization.
  3. Better error handling and state management in the useSchedules hook.
  4. More flexible query building with the defaultSchedulesQueryBuilder function.
  5. Improved context usage with default values in SchedulesContext.

These changes align well with the PR objective of simplifying the loading of transactions and set a solid foundation for future improvements, such as implementing the hook on the desktop transaction list.

Great work on this refactoring!


29-54: LGTM with a minor consideration: loadStatuses function improvements.

The refactored loadStatuses function enhances error handling and flexibility. The use of a Map for schedule statuses is an excellent choice for efficient lookups.

However, please note that upcomingLength is passed as a string to getStatus. Consider converting it to a number before passing it to ensure type consistency.

To verify the getStatus function's signature and usage, run the following script:

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

32-89: Well-implemented useTransactions hook

The useTransactions hook is well-structured and efficiently manages the fetching and state of transaction data. Notable points:

  1. Effective use of useState for managing state.
  2. Smart usage of useRef for pagedQueryRef and optionsRef to avoid unnecessary re-renders.
  3. Memoization of the updateQuery function for performance optimization.
  4. Comprehensive error handling and loading state management.
  5. Clean-up logic to prevent memory leaks and race conditions.

The implementation follows React best practices and provides a flexible and efficient way to manage transaction data. Good job!


103-182: Well-implemented usePreviewTransactions hook with improvements

The usePreviewTransactions hook is well-structured and effectively manages the fetching and processing of preview transactions. Notable improvements and good practices:

  1. Effective use of useCachedSchedules hook for schedule data.
  2. Comprehensive error handling and loading state management.
  3. Use of isUnmounted flag to prevent updates on unmounted components.
  4. Complex but necessary logic for creating and processing preview transactions.
  5. Proper error handling in the Promise.all call, addressing a previous review comment.

The implementation follows React best practices and provides a robust way to manage preview transactions. The hook has been appropriately named usePreviewTransactions, addressing a previous suggestion. Good job on the improvements and addressing past review comments!


184-190: Concise and effective isForPreview helper function

The isForPreview function is well-implemented:

  1. It has a clear and specific purpose: determining if a schedule is eligible for preview.
  2. The logic is straightforward and easy to understand.
  3. It correctly checks both the completion status and the relevant statuses for preview eligibility.

This helper function is concise, focused, and effectively serves its purpose in the context of the usePreviewTransactions hook.


1-190: Overall excellent implementation with room for minor optimization

This file demonstrates high-quality React hook implementations for managing transactions and preview transactions. Key strengths include:

  1. Well-structured and efficient custom hooks (useTransactions and usePreviewTransactions).
  2. Effective use of React hooks and best practices.
  3. Comprehensive error handling and state management.
  4. Addressed previous review comments, showing commitment to code improvement.

The only suggested improvement is to optimize the default query for better performance, especially with large datasets.

Great job on creating robust and maintainable code! The minor optimization suggestion aside, this implementation provides a solid foundation for transaction management in the application.

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

23-29: Improved data management and performance

The changes to imports and the use of useMemo for schedulesQueryBuilder are good improvements. They should lead to better performance and more efficient data management. The switch from transform to queryBuilder in SchedulesProvider also suggests a more flexible approach to data querying.

Also applies to: 59-62, 81-81


Line range hint 106-155: Performance optimization through memoized callbacks

The refactoring of callback functions to use useCallback is a great improvement. This change should help prevent unnecessary re-renders and optimize the component's performance. The dependencies for each callback are correctly specified, ensuring they're only recreated when necessary.


217-222: Enhanced type safety and flexibility

The changes to the prop types for accountId and accountName improve both type safety and flexibility of the TransactionListWithPreviews component. This allows the component to handle a wider range of account types while maintaining strong typing, which should lead to fewer runtime errors and better developer experience.

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

4-4: Improved type safety and maintainability

The addition of the getPrimaryOrderBy and Query type imports, along with the new type definitions for Data, Listener, LiveQueryOptions, and PagedQueryOptions, significantly enhances the type safety and maintainability of the code. The use of ReadonlyArray for the Data type is a particularly good practice for ensuring immutability.

Also applies to: 48-57, 242-244

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

140-140: LGTM: Improved API structure for pagedQuery

The update to use an object parameter structure with a named onData callback enhances code readability and aligns with the PR's goal of simplifying transaction handling. This change provides a more explicit and self-documenting API.


156-159: LGTM: Enhanced parameter structure for pagedQuery

The updated pagedQuery call with a structured object parameter improves code organization. Separating the onData callback and options object enhances readability and maintainability. This change aligns well with the PR's objective of simplifying transaction handling.


174-177: LGTM: Consistent application of new pagedQuery structure

This change maintains consistency with the updated pagedQuery API structure throughout the test file. The clear separation of the onData callback and options object enhances readability and aligns with the PR's objectives.


200-202: LGTM: Consistent implementation of new pagedQuery structure

The update to the pagedQuery call maintains consistency with the new API structure throughout the test suite. This change contributes to improved code readability and maintainability.


230-233: LGTM: Uniform application of new pagedQuery structure

This change consistently applies the new pagedQuery API structure, enhancing code readability and maintainability throughout the test suite. The clear separation of onData callback and options aligns well with the PR's objectives.


254-257: LGTM: Consistent use of updated pagedQuery structure

The modification to the pagedQuery call maintains consistency with the new API structure throughout the test file. This change contributes to improved code clarity and aligns with the PR's goals of simplifying transaction handling.


279-281: LGTM: Flexible application of new pagedQuery structure

This update to the pagedQuery call demonstrates the flexibility of the new API structure. By including only the necessary onData callback, it shows how the new structure can be adapted to different use cases while maintaining consistency with the overall API changes.


300-301: LGTM: Consistent implementation of new pagedQuery structure

The update to the pagedQuery call maintains consistency with the new API structure. This change contributes to improved code readability and aligns with the PR's objectives of simplifying transaction handling.


313-313: LGTM: Improved pagedQuery return object structure

The updates to the pagedQuery return object (totalCount, hasNext, data) provide a more intuitive and straightforward API. These changes enhance code readability and simplify pagination handling, aligning well with the PR's objectives. The consistent application of these changes throughout the test file is commendable.

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


361-363: LGTM: Consistent application of new pagedQuery structure

This update to the pagedQuery call maintains consistency with the new API structure throughout the test suite. The clear separation of the onData callback and options object enhances code readability and aligns with the PR's objectives of simplifying transaction handling.

.eslintrc.js (1)

164-169: LGTM! The changes align with the specified requirements.

The update to the react-hooks/exhaustive-deps rule, specifically adding useQuery to the additionalHooks option, is correct and aligns with the retrieved learning. This implementation ensures that only the useQuery hook is considered, rather than using a more general pattern.

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

Line range hint 1-1530: Overall, good improvements to asynchronous data handling.

The changes made to this file, particularly in the PayeeIcons component, represent a positive step towards more robust handling of asynchronous data. While the file remains complex, these modifications enhance its reliability and should reduce the likelihood of errors related to undefined schedule data.

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

1-7: Imports are well-organized and correctly added

The introduction of useDebounceCallback and useTransactions through proper imports enhances functionality and maintains code clarity.


27-33: Efficient use of useCallback to memoize baseTransactionsQuery

Memoizing baseTransactionsQuery with useCallback ensures the function is only re-created when category or month changes, improving performance.


35-43: Proper integration of the useTransactions hook

Leveraging the useTransactions hook simplifies transaction data handling, and the provided queryBuilder effectively constructs the necessary query.


45-45: Default date format fallback implemented

Using a default date format ensures functionality even when useDateFormat doesn't return a value.


48-63: Effective synchronization with data changes using useEffect

The useEffect hook correctly sets up a listener for 'sync-event', and the dependency array ensures the effect runs appropriately when dispatch or reloadTransactions changes.


68-76: Debounced search query updater enhances performance

Using useDebounceCallback with a 150ms delay efficiently manages search input, reducing unnecessary updates. Dependencies are correctly specified.


81-86: onSearch callback properly utilizes debounced update

The onSearch handler correctly invokes the debounced updateSearchQuery, and dependencies ensure proper function memoization.


88-96: Corrected onOpenTransaction function

The onOpenTransaction function accurately navigates to transaction details when the transaction is not a preview, and dependencies are properly set in useCallback.


128-129: Appropriate handlers passed to TransactionListWithBalances

Passing loadMoreTransactions and onOpenTransaction as props ensures proper interaction functionality within the transaction list.

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

344-347: Confirm unsubscription in liveQuery with new syntax

As in the previous instance, please confirm that the live.unsubscribe method remains functional when using the onData property in the liveQuery function at lines 344-347. Ensuring proper unsubscription is essential for application performance and resource management.


301-304: Ensure liveQuery unsubscription works with object syntax

With the change to use an object with an onData property in the liveQuery function at lines 301-304, please verify that the live.unsubscribe method still works correctly with this syntax. It's important to ensure that unsubscribing functions as expected to prevent potential memory leaks from lingering subscriptions.

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

219-219: Consider using useLayoutEffect to handle side effects

Based on previous learnings, useLayoutEffect was used in AllTransactions to dispatch actions when prependTransactions changed. Currently, it's not being used. Verify if omitting useLayoutEffect affects any synchronous updates needed before rendering.

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 (3)
packages/loot-core/src/client/query-helpers.ts (3)

10-45: Enhanced function signatures for better type safety and readability

The updated signatures for liveQuery and pagedQuery functions significantly improve type safety and readability. The use of generic types and structured parameter objects makes the function usage more clear and allows for better type inference.

Consider adding JSDoc comments to these functions to provide more context about their usage and parameters. This would further improve the developer experience when using these functions.


60-238: Improved LiveQuery class with better encapsulation and type safety

The refactoring of the LiveQuery class significantly enhances its structure, type safety, and encapsulation. The introduction of private properties, generic types, and new methods for data management and error handling provides better control over the query lifecycle.

The runLiveQuery static method is a great addition that simplifies the creation and execution of LiveQuery instances.

Consider adding more detailed error logging in the fetchData method. Instead of just logging "Error fetching data", it would be helpful to include more context about the error, such as the query being executed or any relevant error details.

🧰 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: Enhanced PagedQuery class with improved pagination logic and type safety

The refactoring of the PagedQuery class, including its extension of LiveQuery with generic types and the addition of new private properties for pagination management, significantly improves the pagination logic and type safety. The runPagedQuery static method is a great addition that simplifies the creation and execution of PagedQuery instances.

In the run method, the fetchCount method is called without awaiting its completion. This might result in this._totalCount not being updated before it's needed. Consider updating the run method to await the fetchCount call:

run = () => {
  this.subscribe();

  this._fetchDataPromise = this.fetchData(async () => {
    this._hasReachEnd = false;

    // Also fetch the total count
-   this.fetchCount();
+   await this.fetchCount();

    // ... rest of the method
  });

  return this._fetchDataPromise;
};
🧰 Tools
🪛 Biome

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 9560378 and 88bd218.

📒 Files selected for processing (1)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
🧰 Additional context used
🪛 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 (2)
packages/loot-core/src/client/query-helpers.ts (2)

4-4: Improved type safety with new imports and type definitions

The addition of the getPrimaryOrderBy and Query type imports, along with the new type definitions for Data, Listener, LiveQueryOptions, and PagedQueryOptions, significantly enhances the type safety and readability of the code. These changes provide better structure and clarity to the query-related operations.

Also applies to: 48-57, 242-244


103-104: ⚠️ Potential issue

Initialize _data and _dependencies according to their types

The properties _data and _dependencies are initialized to null, but their types are Data<TResponse> (which is ReadonlyArray<TResponse>) and Set<string>, respectively. Initializing them to null may lead to type inconsistencies.

Apply this diff to fix the initialization:

- this._data = null;
- this._dependencies = null;
+ this._data = [];
+ this._dependencies = new Set<string>();

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

🧹 Outside diff range and nitpick comments (3)
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (1)

93-93: Clarify the commit reference in the comment

The comment references commit 05e58279 for details on handling preview transactions. For better maintainability, consider adding a brief summary of the relevant details or ensure that this reference remains accessible to future developers.

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

180-186: Ensure consistent schedule status checking in isForPreview function

The isForPreview function checks if schedule.completed is falsy, which could lead to unexpected results if schedule.completed is undefined or null. It's safer to explicitly check for false.

Modify the condition to explicitly compare with false:

 function isForPreview(schedule: ScheduleEntity, statuses: ScheduleStatuses) {
   const status = statuses.get(schedule.id);
   return (
-    !schedule.completed &&
+    schedule.completed === false &&
     (status === 'due' || status === 'upcoming' || status === 'missed')
   );
 }

This ensures that only schedules with completed explicitly set to false are considered for preview.


160-165: Enhance error logging in usePreviewTransactions

When an error occurs during the Promise.all operation, the error is caught and stored in state, but it might be helpful to log the error for debugging purposes.

Add console error logging inside the catch block:

 .catch(error => {
   if (!isUnmounted) {
     setIsLoading(false);
     setError(error);
+    console.error('Error in usePreviewTransactions:', error);
   }
 });

This addition aids in debugging by outputting the error to the console.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 88bd218 and b793865.

📒 Files selected for processing (3)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (7 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
🧰 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.
🔇 Additional comments (6)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6)

60-63: LGTM: Proper memoization of schedulesQueryBuilder

The use of useMemo to memoize schedulesQueryBuilder ensures that the builder function is only recreated when accountId changes. This is an appropriate optimization.


244-246: LGTM: Correct use of usePreviewTransactions

The implementation of usePreviewTransactions with the isDisabled option tied to isSearching effectively prevents preview transactions from loading during a search, optimizing performance.


255-259: LGTM: Conditional dispatch in useEffect

Appropriately adding a check for accountId before dispatching markAccountRead(accountId) prevents potential errors when accountId is undefined.


227-228: Ensure queries.transactions(accountId) handles special accountId values

When calling queries.transactions(accountId), verify that the function correctly interprets the special accountId values ('budgeted', 'offbudget', 'uncategorized') and constructs the appropriate queries.

To check how queries.transactions(accountId) handles these values, execute:

#!/bin/bash
# Description: Find the definition of `queries.transactions` and inspect handling of special `accountId` values.

# Test: Locate the function definition. Expect: Handling of string literals.
rg --multiline --multiline-dotall 'queries\.transactions\s*=\s*function\s*\(accountId\)\s*{.*?}' -A 50

304-327: ⚠️ Potential issue

Handle potential errors when splitting transactionId

In onPost and onSkip, transactionId is split without verifying the structure of the resulting array. If transactionId does not contain '/', accessing parts[1] could cause a runtime error. Add error handling to ensure parts[1] exists.

Apply this diff to add necessary checks:

 onPost: async transactionId => {
   const parts = transactionId.split('/');
+  if (parts.length < 2) {
+    console.error('Invalid transactionId format:', transactionId);
+    return;
+  }
   await send('schedule/post-transaction', { id: parts[1] });
   dispatch(collapseModals('scheduled-transaction-menu'));
 },
 onSkip: async transactionId => {
   const parts = transactionId.split('/');
+  if (parts.length < 2) {
+    console.error('Invalid transactionId format:', transactionId);
+    return;
+  }
   await send('schedule/skip-next-date', { id: parts[1] });
   dispatch(collapseModals('scheduled-transaction-menu'));
 },

Likely invalid or redundant comment.


218-223: Clarify the type of accountId

Expanding accountId to include 'budgeted', 'offbudget', and 'uncategorized' allows for broader functionality. Ensure that all usages of accountId handle these string literals appropriately, and that functions consuming accountId can process both account IDs and these specific strings without issues.

To confirm that all functions handle the expanded accountId types correctly, run the following script:

✅ Verification successful

All usages of accountId correctly handle the new string literals.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for all usages of `accountId` in functions and ensure they handle string literals.

# Test: Find all functions that use `accountId`. Expect: Proper handling of string literals.
rg --multiline --multiline-dotall 'function\s+\w+\s*\(.*accountId.*\)\s*{.*?}' -A 10

# Test: Search for direct comparisons of `accountId` with strings. Expect: Comparisons include new literals.
rg "accountId\s*===\s*['\"](budgeted|offbudget|uncategorized)['\"]"

Length of output: 1306


Script:

#!/bin/bash
# Description: Verify that all functions using `accountId` handle the new string literals appropriately.

# Test 1: Find all function implementations that include `accountId` as a parameter
rg --multiline --multiline-dotall 'function\s+\w+\s*\([^)]*\baccountId\b[^)]*\)\s*\{[^}]*\}' .

# Test 2: Find all arrow functions that include `accountId` as a parameter
rg --multiline --multiline-dotall '\([^)]*\baccountId\b[^)]*\)\s*=>\s*\{[^}]*\}' .

# Test 3: Search for direct comparisons of `accountId` with the new string literals
rg "accountId\s*===\s*['\"](budgeted|offbudget|uncategorized)['\"]" .

Length of output: 24137

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

🧹 Outside diff range and nitpick comments (1)
packages/loot-core/src/client/data-hooks/transactions.ts (1)

157-160: Log errors for better debugging in usePreviewTransactions

Currently, when an error occurs during the fetching of preview transactions, the error is set but not logged. Logging the error can aid in debugging and provide more context during development.

Consider adding a console error log:

 .catch(error => {
   if (!isUnmounted) {
     setIsLoading(false);
     setError(error);
+    console.error('Error in usePreviewTransactions:', error);
   }
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b793865 and f0a0e2a.

📒 Files selected for processing (3)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx
🧰 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.
🔇 Additional comments (5)
packages/loot-core/src/client/data-hooks/transactions.ts (1)

106-124: 🛠️ Refactor suggestion

Optimize scheduleTransactions computation with useMemo

The scheduleTransactions array is recalculated on every render, even if isSchedulesLoading, schedules, and statuses haven't changed. This can be inefficient, especially with a large number of schedules.

Memoize scheduleTransactions using useMemo to prevent unnecessary recalculations:

-const scheduleTransactions = useMemo(() => {
+const scheduleTransactions = useMemo(() => {
   if (isSchedulesLoading) {
     return [];
   }

   // Existing code
   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]);
+}, [isSchedulesLoading, schedules, statuses]);

Likely invalid or redundant comment.

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

26-29: Good use of hooks for transaction management

The introduction of usePreviewTransactions and useTransactions hooks simplifies transaction fetching and state management.


60-63: Efficient use of useMemo for schedulesQueryBuilder

Using useMemo to memoize schedulesQueryBuilder based on accountId optimizes performance by avoiding unnecessary recalculations.


218-223: Updated accountId types enhance flexibility

The updated type definitions for accountId correctly accommodate special account identifiers like 'budgeted', 'offbudget', and 'uncategorized', enhancing the component's flexibility.


255-259: Proper handling of accountId in useEffect

The useEffect hook appropriately checks for the presence of accountId before dispatching markAccountRead, preventing potential issues when accountId is undefined.

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

🧹 Outside diff range and nitpick comments (3)
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (2)

30-37: Consider optimizing field selection in the query

The use of useCallback for baseTransactionsQuery is a good optimization. However, selecting all fields ('*') might impact performance if not all fields are needed. Consider selecting only the required fields to optimize data transfer and processing.


54-69: LGTM: Simplified sync event handling

The use of reloadTransactions from the useTransactions hook simplifies sync event handling. The effect dependencies are correctly updated.

Consider consolidating the table checks to reduce duplication:

const transactionTables = ['transactions', 'category_mapping', 'payee_mapping'];
const payeeTables = ['payees', 'payee_mapping'];

if (tables.some(table => transactionTables.includes(table))) {
  reloadTransactions?.();
}

if (tables.some(table => payeeTables.includes(table))) {
  dispatch(getPayees());
}

This refactoring improves readability and makes it easier to manage the conditions.

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

195-208: Trim search input to handle whitespace-only queries

In the useTransactionsSearch hook, the searchText isn't trimmed before checking if it's empty. This means inputs with only whitespace characters will be treated as valid search queries, potentially leading to unnecessary search operations.

Consider trimming searchText before processing:

        (searchText: string) => {
+         const trimmedSearchText = searchText.trim();
+
-         if (searchText === '') {
+         if (trimmedSearchText === '') {
            resetQuery();
            setIsSearching(false);
-         } else if (searchText) {
+         } else if (trimmedSearchText) {
            updateQuery(previousQuery =>
-             queries.transactionsSearch(previousQuery, searchText, dateFormat),
+             queries.transactionsSearch(previousQuery, trimmedSearchText, dateFormat),
            );
            setIsSearching(true);
          }
        },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between f0a0e2a and f0ec201.

📒 Files selected for processing (3)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
🧰 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-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.
🔇 Additional comments (12)
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (6)

1-10: LGTM: New imports align with PR objectives

The addition of useTransactions and useTransactionsSearch hooks, along with useDebounceCallback, aligns well with the PR's goal of simplifying transaction loading and improving search functionality.


39-49: LGTM: Effective use of useTransactions hook

The implementation of useTransactions hook simplifies state management for transactions, loading state, and pagination. This change aligns perfectly with the PR's objective of using the new hook to simplify transaction loading.


71-76: LGTM: Efficient implementation of search functionality

The use of useTransactionsSearch hook along with useDebounceCallback is an excellent implementation for search functionality. It aligns with the PR's goal of simplifying transaction management and provides performance optimization by debouncing search requests.


78-85: LGTM: Optimized transaction opening logic

The onOpenTransaction function is well-implemented with useCallback for optimization. The check for preview transactions before navigation is a good practice to prevent potential issues with invalid routes.


118-119: LGTM: Proper usage of hook-provided functions

The TransactionListWithBalances component now correctly uses the loadMoreTransactions function from the useTransactions hook and the memoized onOpenTransaction function. This change aligns well with the new hook-based approach for managing transactions and can help optimize performance.


Line range hint 1-132: Overall assessment: Well-implemented changes that meet PR objectives

The changes in this file effectively implement the new useTransactions and useTransactionsSearch hooks, simplifying transaction loading and management as intended. The code follows good React practices, including the use of useCallback for optimization and proper effect dependencies.

A few minor optimization suggestions were provided, but overall, the implementation is solid and aligns well with the PR objectives.

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

23-30: Improved transaction management with new hooks

The introduction of defaultSchedulesQueryBuilder, usePreviewTransactions, useTransactions, and useTransactionsSearch hooks indicates a shift towards more modular and efficient transaction handling. This change should lead to improved code organization and potentially better performance.


61-64: Optimized schedules query handling

The use of useMemo for schedulesQueryBuilder and the updated SchedulesProvider prop improve the efficiency of schedule queries. This change aligns well with the new transaction management approach.

Also applies to: 83-83


Line range hint 108-157: Enhanced performance with memoized callbacks

The refactoring of onSave, onEditNotes, onCloseAccount, onReopenAccount, and onClick to use useCallback is a good performance optimization. The dependency arrays are correctly specified, ensuring that these callbacks are only recreated when necessary.


226-242: Improved transaction management with custom hooks

The refactoring to use useTransactions, usePreviewTransactions, and useTransactionsSearch hooks significantly improves the component's structure and should lead to better performance and maintainability. The separation of concerns between fetching transactions, handling previews, and managing search is well-implemented.

Also applies to: 244-245, 279-283


317-319: ⚠️ Potential issue

Fix typo in variable name

There's a typo in the variable name transcationsToDisplay. It should be corrected to transactionsToDisplay for clarity and to prevent potential errors.

Apply this diff to correct the typo:

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

Likely invalid or redundant comment.


295-303: ⚠️ Potential issue

Add error handling for transaction ID splitting

In onPost and onSkip, transactionId is split using '/', and parts[1] is used without verifying its existence. This could lead to runtime errors if transactionId doesn't contain '/'. Consider adding error handling:

 onPost: async transactionId => {
   const parts = transactionId.split('/');
+  if (parts.length < 2) {
+    console.error('Invalid transactionId format:', transactionId);
+    return;
+  }
   await send('schedule/post-transaction', { id: parts[1] });
   dispatch(collapseModals('scheduled-transaction-menu'));
 },
 onSkip: async transactionId => {
   const parts = transactionId.split('/');
+  if (parts.length < 2) {
+    console.error('Invalid transactionId format:', transactionId);
+    return;
+  }
   await send('schedule/skip-next-date', { id: parts[1] });
   dispatch(collapseModals('scheduled-transaction-menu'));
 },

This change will prevent potential runtime errors and provide better debugging information if an invalid transactionId is encountered.

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

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

Line range hint 12-12: Potential type mismatch in return statement.

The useMemo call still treats data as an array, but the type annotation for useQuery has changed to NoteEntity instead of NoteEntity[]. This inconsistency could lead to runtime errors.

Consider updating the return statement to handle both array and single entity cases:

return useMemo(() => {
  if (Array.isArray(data)) {
    return data.length > 0 ? data[0].note : null;
  }
  return data ? data.note : null;
}, [data]);

Alternatively, if data is always expected to be an array, update the type annotation in the useQuery call to NoteEntity[].

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

11-13: LGTM: Excellent addition for test stability.

The waitFor method is a valuable addition that will improve the reliability of tests by ensuring the account list is ready before proceeding. This is crucial for preventing race conditions in end-to-end tests.

Consider adding a timeout and error handling for robustness:

async waitFor(timeout = 5000) {
  try {
    await this.accountList.waitFor({ timeout });
  } catch (error) {
    throw new Error(`Account list did not appear within ${timeout}ms: ${error.message}`);
  }
}

This suggestion adds a configurable timeout and provides a more informative error message if the wait fails.


11-13: Consider adding a comment for the waitFor method.

To improve code documentation, consider adding a brief comment explaining the purpose of the waitFor method. For example:

/**
 * Waits for the account list to be present on the page.
 * This method should be called before interacting with the account list
 * to ensure the page is fully loaded.
 */
async waitFor() {
  await this.accountList.waitFor();
}

This comment would help other developers understand when and why to use this method in their tests.

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

19-20: Improved hook usage with loading state.

The updated usage of useSchedules hook with default value and loading state is a good improvement. It enhances error prevention and allows for better loading state management.

Consider using object destructuring with default values for better readability:

const { schedules = [], isLoading = false } = useSchedules();

This explicitly sets a default value for isLoading as well, making the code more self-documenting.


21-23: Good addition of loading state check.

The new conditional check for isLoading is a valuable addition. It prevents rendering incomplete data, improving the user experience.

Consider adding a loading indicator instead of returning null to provide visual feedback to the user:

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

This would give users a clear indication that data is being fetched, enhancing the overall user experience.

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

36-38: LGTM: Useful addition for search functionality testing.

The clearSearch() method is a valuable addition that complements the existing searchByText() method. It provides a clean way to reset the search state, which is crucial for thorough testing of search functionality.

Consider adding a comment to explain the purpose of this method, similar to other methods in the class:

+  /**
+   * Clear the search input
+   */
  async clearSearch() {
    await this.searchBox.clear();
  }
packages/loot-core/src/client/data-hooks/reports.ts (1)

57-60: LGTM: Enhanced memoization and null safety.

The changes in the return statement of useReports are well-implemented:

  1. Directly returning isLoading allows consumers of this hook to easily handle loading states.
  2. The null check queryData ? [...queryData] : [] adds robustness to the sorting logic.
  3. Including isLoading in the useMemo dependency array ensures proper updates.

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

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

This achieves the same result but in a more idiomatic way.

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

57-59: LGTM: Improved test coverage for search functionality

The addition of clearSearch() and the subsequent assertion enhance the test coverage by verifying the search clear functionality. This is a valuable improvement to the test suite.

Consider adding a short comment explaining the purpose of this block:

// Verify that clearing the search restores all transactions
await accountPage.clearSearch();
await expect(accountPage.transactions).not.toHaveCount(0);

This would improve the readability and maintainability of the test.

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

16-18: LGTM: Deprecation comment added.

The deprecation comment is clear and helpful. It guides developers to use the preferred implementation from loot-core/client/data-hooks/transactions.

Consider adding a reason for the deprecation to provide more context. For example:

/**
 * @deprecated Please use `usePreviewTransactions` hook from `loot-core/client/data-hooks/transactions` instead.
 * This hook is being deprecated to centralize transaction-related hooks and improve maintainability.
 */

26-65: LGTM: Hook implementation improved.

The changes to the usePreviewTransactions hook improve its efficiency and readability:

  1. Using usePrevious simplifies state management.
  2. The loading check prevents unnecessary processing.
  3. The refined condition for processing schedules improves efficiency.

Consider extracting the schedule processing logic into a separate function to improve readability further. For example:

const processSchedules = useCallback((schedules: ScheduleEntity[], statuses: ScheduleStatuses) => {
  const baseTrans = schedules.map(/* ... */);
  return Promise.all(baseTrans.map(transaction => send('rules-run', { transaction })))
    .then(/* ... */);
}, []);

// In the main hook
if (scheduleData && scheduleData !== previousScheduleData) {
  const schedules = scheduleData.schedules.filter(s => isForPreview(s, scheduleData.statuses));
  processSchedules(schedules, scheduleData.statuses);
}

This would make the main hook body more concise and easier to understand.


Line range hint 1-85: Overall improvements with clear deprecation path.

The changes to usePreviewTransactions improve its implementation while clearly marking it as deprecated. This approach maintains backwards compatibility while guiding users to the new version.

Consider the following next steps:

  1. Ensure the new implementation in loot-core/client/data-hooks/transactions is fully tested and documented.
  2. Plan for the complete removal of this deprecated hook in a future release.
  3. Update any components still using this hook to use the new implementation.
  4. Add a console warning in development mode to actively notify developers about the deprecation when this hook is used.

Example for adding a console warning:

export function usePreviewTransactions(
  collapseTransactions?: (ids: string[]) => void,
) {
  if (process.env.NODE_ENV === 'development') {
    console.warn(
      'usePreviewTransactions is deprecated. Please use the hook from loot-core/client/data-hooks/transactions instead.'
    );
  }
  // ... rest of the hook implementation
}
packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (2)

36-40: Improved hook usage and query building.

The changes to useSchedules hook usage and the introduction of a queryBuilder callback are good improvements. They enhance flexibility and follow React best practices.

Consider destructuring isLoading and schedules directly in the function parameters for cleaner code:

const { isLoading: isSchedulesLoading, schedules } = useSchedules({
  queryBuilder: useCallback(
    (q: Query) => q.filter({ id: scheduleId }),
    [scheduleId],
  ),
});

54-54: Improved null safety, but consider better user feedback.

The use of optional chaining for accessing schedule properties and providing a fallback empty string for format function are good improvements for code robustness.

Consider providing more informative fallback text for better user experience:

title={<ModalTitle title={schedule?.name || 'Unnamed Schedule'} shrinkOnOverflow />}
// ...
{format(schedule?.next_date || '', 'MMMM dd, yyyy') || 'No date available'}

Also applies to: 68-68

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

42-42: Excellent update to the useSchedules hook usage!

The change from transform to queryBuilder improves the API consistency of the useSchedules hook. The use of useCallback for memoization is a great practice, potentially optimizing performance by preventing unnecessary re-renders. The query now efficiently filters out completed schedules, which aligns well with the component's purpose.

Consider adding a comment explaining the purpose of filtering out completed schedules, as it might not be immediately obvious to other developers why this filtering is necessary.

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

30-37: Improved query structure and memoization

The renaming and restructuring of the baseTransactionsQuery function enhance clarity and performance. The use of useCallback for memoization is a good practice to prevent unnecessary re-renders.

Consider renaming the function to createBaseTransactionsQuery to better reflect its action of creating a query, following the common convention for factory functions.


54-69: Sync event handling updated, but could be further optimized

The update to use reloadTransactions from the useTransactions hook is a good improvement. However, the table checks could be optimized for better readability and maintainability.

Consider consolidating the table checks:

const transactionTables = ['transactions', 'category_mapping', 'payee_mapping'];
const payeeTables = ['payees', 'payee_mapping'];

if (tables.some(table => transactionTables.includes(table))) {
  reloadTransactions?.();
}

if (tables.some(table => payeeTables.includes(table))) {
  dispatch(getPayees());
}

This approach reduces duplication and makes it easier to manage the list of tables that trigger each action.

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

114-114: Consistent improvement in test reliability, consider unified refactoring.

The addition of await accountPage.waitFor(); after account creation is consistent with the previous change and improves test reliability for the import tests.

To maintain this consistency while improving code maintainability, consider implementing the refactoring suggestion from the previous review. This could involve modifying the navigation.createAccount() method to include the wait, eliminating the need for explicit wait calls in individual tests.


65-66: Overall improvement in test reliability with potential for further enhancement.

The additions of await accountPage.waitFor(); in both locations significantly improve the reliability of the test suite by ensuring pages are fully loaded before proceeding with test actions. This is a positive change that helps prevent potential race conditions and flaky tests.

To further enhance the maintainability and consistency of the test suite, consider implementing the refactoring suggestion from the previous review. This would involve moving the wait logic into the navigation and account creation methods, which could provide the following benefits:

  1. Reduced duplication of wait calls throughout the test suite.
  2. Consistent behavior across all tests that use these methods.
  3. Simplified test cases that don't need to worry about explicit waits.

This refactoring would be a valuable next step in improving the overall quality and maintainability of the test suite.

Also applies to: 114-114

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

71-76: Improved data fetching and error handling. Consider further optimization.

The changes effectively improve the data fetching process and error handling:

  1. Using useCachedSchedules hook enhances data management.
  2. Handling the loading state prevents rendering issues when data is not ready.
  3. Providing a default empty array for schedules is a good practice to avoid potential undefined errors.

These improvements align well with the PR objectives of simplifying transaction loading.

Consider memoizing the useCachedSchedules call to optimize performance, especially if this component re-renders frequently:

const { isLoading, schedules = [] } = useMemo(() => useCachedSchedules(), []);

This ensures that the hook is only called when the component mounts, potentially reducing unnecessary re-renders.

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

33-35: LGTM! Consider using waitFor() in other methods.

The new waitFor() method is a valuable addition that enhances test reliability by ensuring the transaction table is ready before interactions.

As suggested in the past review, consider incorporating this method in other parts of the class that interact with the transaction table. For example:

async createSingleTransaction(transaction) {
  await this.waitFor();
  await this.enterSingleTransaction(transaction);
  await this.addEnteredTransaction();
}

async selectNthTransaction(index) {
  await this.waitFor();
  const row = this.transactionTableRow.nth(index);
  await row.getByTestId('select').click();
}

This would further improve the robustness of your e2e tests.


33-35: Consider a strategic approach to using waitFor()

While the waitFor() method is a valuable addition, its effectiveness could be maximized by strategically incorporating it throughout the class.

Consider the following approach:

  1. Identify key methods that initiate interaction with the transaction table, such as createSingleTransaction, createSplitTransaction, and selectNthTransaction.
  2. Add await this.waitFor(); at the beginning of these methods.
  3. For methods that are often called in sequence (like enterSingleTransaction followed by addEnteredTransaction), consider adding waitFor() only to the first method in the sequence.
  4. Document the usage of waitFor() in the class, explaining that methods prefixed with it ensure the transaction table is ready.

This approach balances improved test reliability with performance considerations.

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

187-187: Accessibility improvement: Good addition of aria-label

The addition of the aria-label attribute to the View component is a positive change that enhances accessibility for screen reader users. This label accurately describes the content as an "Account list".

Consider the following additional accessibility improvements:

  1. Add role="list" to this View component to explicitly define its purpose.
  2. For each account item, wrap it in a View component with role="listitem".
  3. Ensure that interactive elements (like buttons) have appropriate aria-label attributes, especially if they use icon-only content.

Example implementation:

<View aria-label="Account list" role="list" style={{ margin: 10 }}>
  {budgetedAccounts.map(acct => (
    <View key={acct.id} role="listitem">
      <AccountCard
        // ... other props ...
      />
    </View>
  ))}
  {/* Similar change for offbudgetAccounts */}
</View>

Line range hint 1-287: Consider additional accessibility improvements throughout the file

While the addition of the aria-label to the account list is a good start, there are several other areas where accessibility could be improved:

  1. In the AccountCard component:

    • Add an aria-label to the Button component that includes the account name and balance.
    • Consider using aria-live for updating balance information.
  2. In the EmptyMessage component:

    • Use a heading (e.g., <h2>) to structure the content.
  3. For the PullToRefresh component:

    • Ensure it has proper ARIA attributes for its functionality.
  4. In the MobilePageHeader:

    • Add an aria-label to the "Add account" button.
  5. Throughout the file:

    • Ensure color contrast ratios meet WCAG 2.1 standards.
    • Verify that all text sizes are easily readable on mobile devices.

Here's an example of how you might improve the AccountCard component:

function AccountCard({ account, /* other props */ }) {
  return (
    <Button
      onPress={() => onSelect(account.id)}
      aria-label={`${account.name} account with balance of ${/* formatted balance */}`}
      // ... other props
    >
      {/* ... existing content ... */}
    </Button>
  );
}

Consider implementing similar improvements throughout the file to enhance overall accessibility.

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

Line range hint 108-157: Optimized callback functions with useCallback

The use of useCallback for onSave, onEditNotes, onCloseAccount, onReopenAccount, and onClick is a good performance optimization. This prevents unnecessary re-creation of these functions on each render.

Consider further optimizing by memoizing the pushModal arguments object:

const menuOptions = useMemo(() => ({
  accountId: account.id,
  onSave,
  onEditNotes,
  onCloseAccount,
  onReopenAccount,
}), [account.id, onSave, onEditNotes, onCloseAccount, onReopenAccount]);

const onClick = useCallback(() => {
  dispatch(pushModal('account-menu', menuOptions));
}, [dispatch, menuOptions]);

This change could potentially reduce unnecessary object creation if onClick is called frequently.

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

253-253: Consistent immutability for items array

The change to readonly SchedulesTableItem[] for the items variable is a good improvement, making it consistent with the schedules prop type change. This ensures that the derived items array cannot be accidentally modified within the component, enhancing type safety.

For complete consistency, consider also updating the return type of the useMemo hook:

const items: readonly SchedulesTableItem[] = useMemo<readonly SchedulesTableItem[]>(() => {
  // ... existing logic ...
}, [filteredSchedules, showCompleted, allowCompleted]);

This explicit type annotation on useMemo will ensure that TypeScript infers the correct readonly type throughout the entire memoization process.

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

312-317: Refactor: Destructure useSchedules hook return value

The useSchedules hook usage has been updated to destructure its return value. This change improves code readability and makes it clear which properties are being used from the hook's return value.

Consider adding TypeScript types for the useSchedules hook return value to improve type safety and developer experience. For example:

interface SchedulesHookResult {
  schedules: Schedule[];
  statuses: Map<string, ScheduleStatus>;
  isLoading: boolean;
}

const {
  schedules,
  statuses: scheduleStatuses,
  isLoading: isSchedulesLoading,
}: SchedulesHookResult = useSchedules({
  queryBuilder: useCallback(q => q.filter({ id }), [id]),
});

324-326: Enhance user feedback for empty schedules

When no schedules are found, the component currently displays the id, which may not be informative to the user.

Consider displaying a more user-friendly message when no schedules are found. For example:

- return <View style={{ flex: 1 }}>{id}</View>;
+ return <View style={{ flex: 1 }}>No schedules found for ID: {id}</View>;

312-329: Summary of changes in ScheduleDescription component

The changes to the ScheduleDescription component have improved its structure and error handling. Key improvements include:

  1. Better destructuring of the useSchedules hook return value.
  2. Explicit handling of the loading state.
  3. Simplified retrieval of schedule and status information.

These changes enhance the component's readability and robustness. However, there are opportunities for further improvement:

  1. Adding TypeScript types for better type safety.
  2. Enhancing user feedback when no schedules are found.
  3. Consider adding error handling for cases where the useSchedules hook might fail.

Overall, the changes are positive and align well with React best practices.

To further improve this component, consider:

  1. Implementing error boundaries to catch and handle any potential errors in the useSchedules hook or schedule rendering.
  2. If the id prop is optional, add a prop type check and handle cases where it might be undefined.
  3. Memoize the queryBuilder callback using useCallback to optimize performance, especially if this component re-renders frequently.
packages/loot-core/src/client/data-hooks/filters.ts (1)

8-8: Add type annotation to 'rows' parameter in toJS function

Adding a type annotation to the rows parameter enhances type safety and code clarity.

You can update the function signature as follows:

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

Please replace any[] with the appropriate type if available.

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

Line range hint 90-100: Simplify Parameter Types in 'groupBy' Method

The groupBy method's exprs parameter has a complex union type, which can make the code harder to read and maintain. Consider overloading the method or simplifying the parameter types to improve clarity.

Apply this diff to simplify the method signature:

-groupBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
+groupBy(...exprs: Array<ObjectExpression | string>) {

Update the method body accordingly:

- if (!Array.isArray(exprs)) {
-   exprs = [exprs];
- }
-
  return new Query({
    ...this.state,
-   groupExpressions: [...this.state.groupExpressions, ...exprs],
+   groupExpressions: [...this.state.groupExpressions, ...exprs],
  });

This approach uses rest parameters to accept a variable number of arguments, simplifying the type and removing the need to check if exprs is an array.


Line range hint 101-111: Simplify Parameter Types in 'orderBy' Method

Similar to the groupBy method, the orderBy method has a complex union type for exprs. Simplifying the parameter types can enhance readability and maintainability.

Apply this diff to simplify the method signature:

-orderBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
+orderBy(...exprs: Array<ObjectExpression | string>) {

Update the method body accordingly:

- if (!Array.isArray(exprs)) {
-   exprs = [exprs];
- }
-
  return new Query({
    ...this.state,
-   orderExpressions: [...this.state.orderExpressions, ...exprs],
+   orderExpressions: [...this.state.orderExpressions, ...exprs],
  });

Using rest parameters removes the need for type unions and array checks, making the method signature clearer.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between f0ec201 and 0c4900d.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
📒 Files selected for processing (35)
  • .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/Account.tsx (15 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (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/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/transactions/TransactionsTable.jsx (1 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/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)
💤 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 (2)
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/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`.
🪛 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 (91)
packages/desktop-client/src/hooks/useNotes.ts (2)

3-3: LGTM! Verify consistency across the codebase.

The import statement has been correctly updated to use useQuery from the core library. This change aligns with the broader refactoring effort mentioned in the summary.

Let's verify if this change is consistent across the codebase:

✅ Verification successful

Import statements correctly updated to use useQuery.

All usages of useLiveQuery have been successfully replaced with useQuery, and the imports are consistently sourced from 'loot-core/client/query-hooks' across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining usages of useLiveQuery and confirm useQuery usage

# Test 1: Search for any remaining useLiveQuery imports
echo "Checking for any remaining useLiveQuery imports:"
rg "import.*useLiveQuery"

# Test 2: Confirm useQuery is being imported from the correct path
echo "Confirming useQuery imports:"
rg "import.*useQuery.*from 'loot-core/client/query-hooks'"

Length of output: 371


8-11: Approve with clarification needed on real-time updates and type change.

The implementation has been successfully updated to use useQuery. However, two points need clarification:

  1. Does useQuery support real-time updates like useLiveQuery did? If not, how will this affect the functionality of the useNotes hook?
  2. The type annotation has changed from NoteEntity[] to NoteEntity. Is this intentional? It might affect how the data is handled in the useMemo call below.

To verify the impact of this change, let's check the implementation of useQuery:

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

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

The addition of the useQuery import is consistent with the changes in the function body and aligns with the broader transition from useLiveQuery to useQuery across the codebase.


15-18: LGTM: Improved return statement and dependencies.

These changes enhance the useDashboard hook:

  1. Including isLoading in the returned object allows consumers to easily access the loading state.
  2. The default value for data now ensures an empty array is always returned, preventing potential null pointer exceptions.
  3. Adding isLoading to the useMemo dependency array ensures the memoized object updates when the loading state changes.

These improvements make the hook more robust and easier to use.


8-10: Approve change to useQuery, but verify type annotation.

The switch from useLiveQuery to useQuery is good and consistent with the import change. It improves loading state handling by providing direct access to isLoading.

However, please verify the type annotation:

The type annotation has changed from <Widget[]> to <Widget>. If the query returns an array of widgets, this might cause type issues. Please confirm if this is intentional or if it should be <Widget[]>.

To verify, let's check the query result type:

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

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

This change is consistent with the transition from useLiveQuery to useQuery as mentioned in the summary. It's a necessary import for the modifications in the useWidget function.


7-7: Excellent type refinement in function signature.

The change from id: string to id: W['id'] enhances type safety. It ensures that the id passed to the function matches the expected type for the specific widget, reducing the potential for type-related errors.


8-10: Great improvements in query hook usage and reactivity.

  1. The switch to useQuery aligns with the broader refactoring effort, improving consistency across the codebase.
  2. Destructuring isLoading provides a more direct way to handle loading states.
  3. The default empty array for data prevents potential issues with undefined data.
  4. Including both id and type in the dependency array ensures the query updates appropriately when either value changes, enhancing reactivity.

These changes collectively improve the hook's functionality and reliability.


15-18: Effective optimization of memoized return value.

  1. Directly using isLoading from the query result is more efficient and clearer than deriving it from data.
  2. Including both data and isLoading in the useMemo dependency array ensures the memoized value updates appropriately when either value changes.

These changes improve the hook's performance and reactivity, aligning well with the modifications made to the useQuery hook usage.


Line range hint 1-20: Summary: Excellent refactoring of the useWidget hook.

The changes made to this file are part of a broader refactoring effort and significantly improve the useWidget hook. Key improvements include:

  1. Enhanced type safety with more specific parameter types.
  2. Transition from useLiveQuery to useQuery, aligning with changes across the codebase.
  3. Improved reactivity by updating dependency arrays in both useQuery and useMemo.
  4. More efficient handling of loading states.

These changes collectively result in a more robust, efficient, and maintainable hook. Great work on this refactoring!

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

7-7: LGTM: Good addition for improved page interaction.

The new accountList property enhances the page model by providing a reference to the account list element. Using getByLabel is an excellent choice for accessibility and robust testing.

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

3-3: LGTM: Import statement updated correctly.

The import for useSchedules has been updated to use the new path, which aligns with the PR objective of simplifying transaction loading and suggests an improvement in code organization.


Line range hint 1-38: Overall, excellent improvements to the ScheduleValue component.

The changes in this file align well with the PR objectives and improve the component's robustness. The updated useSchedules hook usage, along with the new loading state management, enhances error handling and user experience. These modifications are consistent with updates in other components across the application, contributing to a more maintainable and reliable codebase.

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

18-20: LGTM: Good addition for improving test reliability.

The waitFor() method is a valuable addition to the MobileAccountPage class. It ensures that the transaction list is fully loaded before any interactions, which can significantly improve the reliability of your end-to-end tests by reducing race conditions and flaky tests.


Line range hint 1-39: Overall, these changes enhance the testability of the mobile account page.

The additions to MobileAccountPage class align well with the PR objectives. They provide better tools for testing transaction loading and management, which indirectly supports the implementation of the new useTransactions hook. These methods will help ensure that the UI changes resulting from the hook implementation are correctly reflected and testable in the mobile view.

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

8-8: LGTM: Appropriate use of useQuery.

The change from useLiveQuery to useQuery is correct. As per the provided context, useLiveQuery is a wrapper around useQuery that only returns the data. By using useQuery directly, you now have access to additional properties like isLoading, which can be beneficial for more granular control over the component's behavior.


39-42: LGTM: Improved state handling with useQuery.

The changes here are well-implemented:

  1. Using useQuery with destructuring provides a cleaner way to access both the data and loading state.
  2. Renaming data to queryData maintains consistency with the rest of the function.
  3. The query logic remains unchanged, ensuring the existing functionality is preserved.

These modifications enhance the hook's ability to handle loading states, which can lead to better user experience when used in components.

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

30-30: LGTM: Improved test reliability

Adding await accountsPage.waitFor(); ensures that the accounts page is fully loaded before proceeding with assertions. This change enhances the test's reliability and reduces the likelihood of flaky tests.


41-42: LGTM: Consistent wait and improved readability

Adding await accountsPage.waitFor(); maintains consistency with the previous test and ensures the accounts page is fully loaded before opening an individual account. The empty line improves code readability by separating logical blocks.


44-44: LGTM: Enhanced test reliability for individual account page

Adding await accountPage.waitFor(); ensures that the individual account page is fully loaded before proceeding with assertions. This change is consistent with previous wait statements and enhances the overall reliability of the test.

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

9-12: LGTM: New type imports added.

The addition of TransactionEntity and ScheduleEntity imports from loot-core/types/models is appropriate for type annotations in the code.


14-14: LGTM: Import for usePrevious hook added.

The addition of the usePrevious hook import is appropriate for the new implementation that replaces local state management.

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

43-46: Improved loading state handling.

The update to use isSchedulesLoading for the loading check and returning null during loading is a good practice. It prevents rendering incomplete content and improves the user experience.


47-48: Enhanced null safety with optional chaining.

The use of optional chaining (schedules?.[0]) to assign the schedule variable is a good defensive programming practice. It prevents potential runtime errors if schedules is undefined or empty.

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

7-10: Excellent use of custom hooks for transaction management

The introduction of useTransactions and useTransactionsSearch hooks is a positive change. This approach aligns with React best practices, promoting better separation of concerns and potentially improving code reusability across the application.


39-49: Effective use of useTransactions hook for simplified state management

The implementation of the useTransactions hook significantly simplifies the component's state management. By encapsulating the transaction loading logic within a custom hook, the code becomes more declarative and easier to maintain. This change aligns well with React's component-based architecture and promotes better separation of concerns.


Line range hint 1-124: Overall assessment: Significant improvements with minor refinements needed

The changes to CategoryTransactions.jsx represent a substantial improvement in code structure, maintainability, and potentially performance. The introduction of custom hooks for transaction management and search functionality aligns well with React best practices and promotes better separation of concerns.

Key improvements:

  1. Use of useTransactions and useTransactionsSearch hooks for better state management.
  2. Memoization of functions and queries to prevent unnecessary re-renders.
  3. More declarative and modular code structure.

Remaining concerns:

  1. The typo in onOpenTransaction function name.
  2. Potential for further optimization in the sync event handling.

Addressing these minor issues will further enhance the overall quality of the component.


71-85: ⚠️ Potential issue

Improved search functionality and callback memoization

The implementation of the useTransactionsSearch hook and the memoization of onOpenTransaction are excellent improvements. These changes enhance the component's performance and maintainability.

However, the typo in the function name onOpenTransaction still exists. Please correct it:

-const onOpenTranasction = useCallback(
+const onOpenTransaction = useCallback(

Also, ensure that all references to this function are updated accordingly.

Likely invalid or redundant comment.

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

65-66: Improved test reliability, consider refactoring as per previous suggestion.

The addition of await accountPage.waitFor(); enhances test reliability by ensuring the account page is fully loaded before proceeding. This is a good practice to prevent potential race conditions.

However, as previously suggested, consider refactoring this wait into the navigation methods (e.g., navigation.goToAccountPage()) for better maintainability and consistency across tests.

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

1-28: Imports and type definitions look good

The imports are appropriate for the functionality provided by this file, and the type definitions are well-structured and descriptive. This sets a solid foundation for the hooks implemented in this file.


216-222: isForPreview function looks good

The isForPreview helper function is well-implemented and correctly determines if a schedule is eligible for preview based on its completion status and associated status. The logic is clear and concise.


1-222: Overall, well-implemented transaction management hooks

This file provides a robust set of hooks for managing transactions, including fetching, previewing, and searching. The code is well-structured and follows good practices. The suggested improvements focus on optimizing performance, enhancing error handling, and improving responsiveness to prop changes.

Key points:

  1. useTransactions: Consider updating the effect dependency array and resetting the error state.
  2. usePreviewTransactions: Optimize error handling and effect dependencies.
  3. useTransactionsSearch: Add debounce functionality for improved performance.
  4. isForPreview: Well-implemented helper function.

Implementing these suggestions will further enhance the reliability and efficiency of the transaction management system.

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

Line range hint 1-287: Summary of review for Accounts.jsx

The addition of the aria-label to the account list View component is a positive change that improves accessibility. However, there are opportunities for further accessibility enhancements throughout the file.

Key points:

  1. The specific change (adding aria-label) is approved and beneficial.
  2. Several components could benefit from additional ARIA attributes and roles.
  3. Consider structuring content with appropriate headings and ARIA landmarks.
  4. Ensure all interactive elements have proper accessibility labels.
  5. Verify color contrast and text readability throughout the component.

Overall, this change is a step in the right direction for improving the application's accessibility. Implementing the suggested additional improvements would further enhance the user experience for all users, including those relying on assistive technologies.

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

31-34: Improved function naming and type safety.

The renaming of getAccountFilter to accountFilter enhances clarity and aligns with modern naming conventions. The updated parameter type for accountId improves type safety. These changes contribute to better code readability and maintainability without altering the existing functionality.


70-72: Enhanced consistency and simplicity in transaction querying.

The renaming of makeTransactionsQuery to transactions simplifies the function name while maintaining clarity. The updated parameter type for accountId improves type safety and consistency with the accountFilter function. The use of the renamed accountFilter function maintains consistency across the codebase. These changes contribute to a more cohesive and maintainable codebase.

Also applies to: 75-78


83-85: Consistent naming convention applied.

The renaming of makeTransactionSearchQuery to transactionsSearch improves clarity and aligns with the naming convention of other functions in the file. This change enhances code readability without altering the existing functionality.


Line range hint 31-85: Summary: Improved consistency and maintainability in transaction querying.

The changes in this file primarily focus on function renaming and parameter type updates, which contribute to a more consistent and maintainable codebase. The updated function names (accountFilter, transactions, transactionsSearch) and parameter types improve code clarity and type safety. These changes align well with the PR objectives of simplifying transaction loading and management, laying a solid foundation for the implementation of the useTransactions hook.

Overall, these modifications enhance the code quality without altering the core functionality, making it easier for developers to work with transaction-related queries in the future.

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

Line range hint 1-359: Summary: Minor changes related to useSchedules hook refactoring.

The changes in this file are minimal and focused on updating the usage of the useSchedules hook. These modifications appear to be part of a larger refactoring effort to improve code organization. The functionality of the ManageRules component should remain unchanged.

Key points:

  1. The import for useSchedules has been updated to use a core library.
  2. The destructuring of the useSchedules hook return value has been simplified.

These changes should improve code maintainability without affecting the component's behavior.


116-116: LGTM! Verify useSchedules hook implementation.

The simplified destructuring of the useSchedules hook return value is good. It aligns with the import change and maintains the default empty array, which is a good practice.

To ensure the useSchedules hook is implemented correctly, please run the following script:

#!/bin/bash
# Description: Check the implementation of useSchedules hook

# Test: Display the content of the useSchedules hook
cat packages/desktop-client/src/hooks/useSchedules.ts || cat packages/desktop-client/src/hooks/useSchedules.tsx || cat loot-core/client/data-hooks/schedules.ts || cat loot-core/client/data-hooks/schedules.tsx

12-12: LGTM! Verify consistency of import across the project.

The update to import useSchedules from loot-core/client/data-hooks/schedules is a good architectural change, potentially improving code organization and reusability.

To ensure consistency, please run the following script to check for any remaining imports from the old location:

✅ Verification successful

Verified: Import consistency confirmed.

All useSchedules imports are correctly updated to loot-core/client/data-hooks/schedules, and no residual references to the old import paths were found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining imports from the old useSchedules location

# Test: Search for old import pattern
rg "from '../hooks/useSchedules'" packages/desktop-client/src

Length of output: 519


Script:

#!/bin/bash
# Description: Locate the definition of useSchedules across the entire codebase

# Search for the definition or export of useSchedules
rg "export .*useSchedules" --glob '!.git' .

Length of output: 133

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

23-30: Improved modularity with new custom hooks

The introduction of defaultSchedulesQueryBuilder and custom hooks (usePreviewTransactions, useTransactions, useTransactionsSearch) enhances the modularity and reusability of the code. This change aligns with best practices for React development by encapsulating complex logic into custom hooks.


61-64: Enhanced schedule handling with queryBuilder

The use of schedulesQueryBuilder and updating SchedulesProvider to use a queryBuilder prop improves the consistency and flexibility of schedule handling. This change allows for better customization of schedule queries based on the account context.

Also applies to: 83-89


226-245: Improved transaction management with custom hooks

The use of useTransactions and usePreviewTransactions hooks has significantly simplified the state management for transactions. This approach improves code readability and maintainability by encapsulating complex logic within these hooks.


279-285: Enhanced search functionality with custom hook

The implementation of useTransactionsSearch and debounced search improves the user experience by providing responsive search functionality while minimizing unnecessary API calls.


Line range hint 1-365: Overall assessment: Significant improvements in code structure and performance

The changes in this file represent a substantial improvement in code structure, maintainability, and performance. Key highlights include:

  1. Introduction of custom hooks for transaction and schedule management.
  2. Improved modularity and reusability of code.
  3. Performance optimizations with useCallback.
  4. Enhanced search functionality with debounced updates.

These changes align well with React best practices and should lead to a more maintainable and performant codebase. The minor issues identified (typo and potential optimization for the sync event listener) can be easily addressed to further improve the code quality.


317-319: ⚠️ Potential issue

Fix typo in variable name

There's a typo in the variable name transcationsToDisplay. It should be corrected to transactionsToDisplay for clarity and to prevent potential errors.

Apply this diff to correct the typo:

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

Likely invalid or redundant comment.

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

33-33: Improved type safety for schedules prop

The change from ScheduleEntity[] to readonly ScheduleEntity[] for the schedules prop type is a good improvement. It enforces immutability at the type level, preventing accidental modifications of the schedules array within the component. This aligns with React's best practices of treating props as immutable and enhances type safety without affecting the runtime behavior.


Line range hint 1-391: Summary: Improved type safety aligns with PR objectives

The changes in this file, while minimal, contribute significantly to the overall code quality and align well with the PR objectives. By enforcing immutability for the schedules prop and the derived items array, these changes enhance type safety and reduce the risk of accidental modifications. This improvement in data handling is consistent with the PR's goal of simplifying transaction management across the application.

These type-level changes lay a solid foundation for further refactoring and the potential implementation of the useTransactions hook in the desktop transaction list, as mentioned in the PR objectives. The increased type safety will make such future changes more robust and less error-prone.

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

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

The change to use an object parameter for doQuery is consistent with updates in other parts of the codebase. This new structure provides more flexibility and clarity when passing options to the query function.


156-159: LGTM: Improved clarity in specifying query options.

The update to use an object parameter for doQuery is consistent with previous changes. The explicit options property enhances readability and allows for more precise configuration of the query behavior.


174-177: LGTM: Consistent update of function call structure.

The changes to the doQuery function call maintain consistency with previous updates. The test's functionality remains intact while adopting the new parameter structure.


200-202: LGTM: Consistent update while maintaining test functionality.

The doQuery function call has been updated to use the new object parameter structure, consistent with previous changes. The test's logic and assertions remain intact, ensuring the functionality is preserved.


230-233: LGTM: Consistent update of function call structure.

The changes to the doQuery function call maintain consistency with previous updates. The test's functionality remains intact while adopting the new parameter structure.


254-257: LGTM: Consistent update of function call structure.

The changes to the doQuery function call maintain consistency with previous updates. The test's functionality remains intact while adopting the new parameter structure.


279-281: LGTM: Consistent update while maintaining test functionality.

The doQuery function call has been updated to use the new object parameter structure, consistent with previous changes. The test's logic and assertions remain intact, ensuring the functionality is preserved.


300-301: LGTM: Updated function call and API usage.

The pagedQuery function call has been updated to use an object parameter structure, consistent with other changes. The test now accesses totalCount and hasNext as properties instead of methods, reflecting an API change in the returned object from pagedQuery. These changes improve consistency and flexibility while maintaining the test's functionality.

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


361-363: LGTM: Improved clarity in specifying query options.

The update to use an object parameter for pagedQuery is consistent with previous changes. The explicit options property enhances readability and allows for more precise configuration of the query behavior, particularly for the pageCount option.


378-380: LGTM: Consistent update of function call structure.

The changes to the pagedQuery function call maintain consistency with previous updates. The test's functionality remains intact while adopting the new parameter structure.


404-407: LGTM: Consistent updates with improved clarity in specifying options.

The pagedQuery function calls have been updated to use the object parameter structure, maintaining consistency with previous changes. The explicit options property enhances readability and allows for more precise configuration of the query behavior, particularly for the pageCount option. The tests' functionality remains intact while adopting these new parameter structures.

Also applies to: 440-443, 484-487


Line range hint 1-524: Overall assessment: Consistent and beneficial refactoring.

The changes in this file are part of a larger effort to refactor the API of query-related functions. The updates consistently modify function calls to use object parameters, which provides several benefits:

  1. Improved readability and maintainability of test cases.
  2. More flexibility in specifying options and callbacks.
  3. Consistency with changes made in other parts of the codebase.

All tests maintain their original functionality while adopting the new parameter structure. This refactoring enhances the overall quality of the codebase without introducing new issues.

.eslintrc.js (2)

164-169: LGTM: Change aligns with the specified requirement.

The modification to add useQuery as an additional hook for the react-hooks/exhaustive-deps rule is correct and aligns with your stated intention. This change will ensure that the useQuery hook is properly checked for exhaustive dependencies.


Line range hint 1-1: Verify presence of no-restricted-imports rule change.

The AI-generated summary mentions a new rule for no-restricted-imports related to react-router-dom's useNavigate hook. However, this change is not visible in the provided code snippet. Could you please confirm if this change was made in a different part of the file or if it's planned for a future update?

To check for the presence of this rule, you can run the following command:

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

320-322: Improved loading state handling

The loading state is now handled more explicitly using the isSchedulesLoading flag from the useSchedules hook. This change ensures that no content is rendered while the schedules are being loaded, which can prevent potential errors and improve user experience.


328-329: Simplified schedule and status retrieval

The code now directly destructures the first schedule from the schedules array and retrieves the corresponding status. This change simplifies the code and makes it more readable.

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

7-11: Introduction of UseQueryResult type enhances code clarity

Defining the UseQueryResult type improves readability by explicitly stating the structure of the object returned from useQuery, making the codebase more maintainable.


14-16: Updated useQuery signature increases hook versatility

Allowing makeQuery to return Query | null and updating the return type to UseQueryResult<Response> enhances the flexibility of the hook, enabling it to handle scenarios where the query may be conditionally unavailable.


23-25: Proper initialization of state based on query presence

Initializing isLoading with query !== null and setting error to undefined ensures that the hook accurately reflects the loading and error states when query is absent, enhancing reliability.


28-29: Correctly updating error state based on query

Setting the error state when query is null and adjusting isLoading accordingly provides clear feedback on the hook's state, improving error handling and user experience.


35-44: Effective prevention of state updates after unmounting

The introduction of the isUnmounted flag successfully prevents state updates on unmounted components, avoiding potential memory leaks and ensuring application stability.


17-21: ⚠️ Potential issue

Reconsider suppressing react-hooks/exhaustive-deps ESLint warning

Ignoring the react-hooks/exhaustive-deps warning might lead to stale or incorrect memoization if makeQuery depends on variables not included in dependencies. Ensure all variables used within makeQuery are specified in the dependencies array to prevent potential bugs.

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

1-1: Approved: Updated React imports

The addition of useCallback and useState from 'react' is appropriate for the hooks used in the component.


3-3: Approved: Imported useDispatch from 'react-redux'

Importing useDispatch is necessary for dispatching actions within the component.


5-5: Approved: Imported pushModal action

Importing pushModal allows the component to dispatch modal-related actions effectively.


22-22: Approved: Initialized dispatch with useDispatch()

Using the useDispatch hook to obtain the dispatch function is correct.


25-30: Approved: Memoized onEdit with useCallback

Memoizing the onEdit function enhances performance by preventing unnecessary re-renders.


32-34: Approved: Memoized onAdd with useCallback

Using useCallback for onAdd is appropriate for optimizing the component.


36-38: Approved: Memoized onDiscover with useCallback

Memoizing the onDiscover function improves performance.


40-68: Add error handling in onAction function

The previous review comment about adding error handling for async send calls is still valid. Consider wrapping the asynchronous operations in a try-catch block to handle potential errors gracefully.


64-65: Approved: Handled unknown action names

Throwing an error in the default case ensures that unexpected action names are properly handled and aids in debugging.


70-70: Approved: Destructured schedules data from useSchedules

Extracting isLoading, schedules, and statuses from the useSchedules hook is appropriate.

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

49-57: Simplify the Type Annotation in 'unfilter' Method

The unfilter method's parameter exprs is typed as Array<keyof ObjectExpression>. Since keyof ObjectExpression resolves to string, consider simplifying the type to string[] for clarity.


67-74: Consider Simplifying the 'select' Method Signature

The select method accepts a complex union of types for exprs. To improve readability and maintainability, consider overloading the method or simplifying the parameter types.


166-168: Type Safety in 'q' Function

The q function initializes a new Query with the provided table. Ensure that the table argument always receives a valid string value consistent with QueryState['table'].


Line range hint 145-161: Ensure Consistent Handling of Order Expressions in 'getPrimaryOrderBy'

The getPrimaryOrderBy function processes orderExpressions from the query state. Verify that all possible types of orderExpressions are correctly handled, especially considering the updated types which now include ObjectExpression and string.

Run the following script to check the types of orderExpressions used throughout the codebase:

This script will help identify if there are usages that might not align with the updated types, allowing you to adjust them as necessary.

✅ Verification successful

Consistent Handling of Order Expressions Verified

The getPrimaryOrderBy function correctly handles both string and ObjectExpression types for orderExpressions. All usages of getPrimaryOrderBy pass valid arguments as per the updated types.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of 'orderBy' and 'getPrimaryOrderBy' to ensure they handle the new types.

# Search for 'orderBy' method calls
rg "orderBy\(" -A 2

# Search for 'getPrimaryOrderBy' function calls
rg "getPrimaryOrderBy\(" -A 2

Length of output: 5460


Script:

#!/bin/bash
# Description: Verify implementation of 'getPrimaryOrderBy' to ensure it handles both ObjectExpression and string types.

# Extract the implementation of 'getPrimaryOrderBy'
ast-grep --pattern $'export function getPrimaryOrderBy($_, $_) {\n  $$$\n}' packages/loot-core/src/shared/query.ts

# Search for all calls to 'getPrimaryOrderBy' and display their argument types
rg "getPrimaryOrderBy\(" --json

Length of output: 3117

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

16-20: ⚠️ Potential issue

Fix syntax error in import statement

The import statement for TransactionEntity, ScheduleEntity, and AccountEntity is missing the from clause, leading to a syntax error. Ensure to specify the module path from which to import these types.

Apply this diff to correct the import statement:

 import {
   type TransactionEntity,
   type ScheduleEntity,
   type AccountEntity,
-},
+} from '../../types/models';

Likely invalid or redundant comment.

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

344-347: Duplicate concern on liveQuery usage with onData property

This change mirrors the modification made in lines 301-304, where liveQuery is called with an object containing an onData property. Please refer to the previous comment to verify that liveQuery supports this usage pattern.


301-304: ⚠️ Potential issue

Verify liveQuery accepts an object with onData property

The liveQuery function call has been modified to pass an object with an onData property:

{
  onData: data =>
    dispatch({ type: 'set-transactions', transactions: data }),
}

Ensure that the liveQuery function is designed to accept an object with an onData callback. If not, this change may lead to unexpected behavior at runtime.

You can verify the expected usage of liveQuery by checking its definition:

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

329-329: Type safety improvement with PagedQuery

The change to use PagedQuery<TransactionEntity> | null for the paged property enhances type safety and clarity.


219-219: Conditional rendering is correctly handled

The condition properly checks for the absence of prependTransactions or the presence of filters before rendering transactions.


489-492: Ensure accountId is handled correctly in makeRootTransactionsQuery

In makeRootTransactionsQuery, the accountId could be undefined. Verify that queries.transactions can handle an undefined accountId without causing errors or unintended behavior.

Run the following script to check how queries.transactions handles an undefined accountId:

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

770-777: Proper handling of schedule loading state

The addition of checking isLoading from useCachedSchedules() and returning null ensures that the component does not render schedule-related UI elements until the schedules are fully loaded. This prevents potential runtime errors due to accessing undefined data.

packages/loot-core/src/client/data-hooks/transactions.ts Outdated Show resolved Hide resolved
packages/loot-core/src/client/data-hooks/transactions.ts Outdated Show resolved Hide resolved
packages/loot-core/src/client/data-hooks/transactions.ts Outdated Show resolved Hide resolved
packages/loot-core/src/client/data-hooks/filters.ts Outdated Show resolved Hide resolved
packages/desktop-client/src/components/schedules/index.tsx Outdated Show resolved Hide resolved
packages/loot-core/src/client/query-helpers.ts Outdated Show resolved Hide resolved
packages/loot-core/src/client/query-helpers.ts Outdated Show resolved Hide resolved
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 (20)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)

20-22: LGTM: Improved useSchedules hook usage with query and loading state.

The changes to the useSchedules hook usage are well-implemented. The addition of the query parameter and the use of useMemo for optimization are good practices. The destructuring with a default empty array for schedules helps prevent potential issues.

Consider moving the useMemo hook outside the useSchedules call for better readability:

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

24-26: Approved: Added loading state handling.

The addition of the loading state check is a good practice. It prevents rendering the component before data is available.

Consider replacing the null return with a loading indicator for better user experience:

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

36-40: Excellent use of hooks for optimized data fetching.

The refactoring of useSchedules to return isLoading and schedules improves state management. The use of useMemo for the query is a great optimization.

Consider adding a comment explaining the structure of transactionId to clarify why split('/')?.[1] is used to extract scheduleId.


47-48: Good use of optional chaining for safe data access.

The use of schedules?.[0] and optional chaining (?.) for accessing schedule properties enhances code safety. This prevents errors when dealing with potentially undefined data.

Consider adding a fallback value for the schedule name and next_date to improve user experience when data is missing:

title={<ModalTitle title={schedule?.name || 'Unnamed 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 (2)

28-35: LGTM: Improved query function with memoization

The baseTransactionsQuery function is a good improvement, using useCallback for memoization. However, consider extracting the query building logic into a separate function outside the component to further improve reusability and reduce unnecessary re-renders.

Consider refactoring like this:

const buildTransactionsQuery = (category, month) =>
  q('transactions')
    .options({ splits: 'inline' })
    .filter(getCategoryMonthFilter(category, month))
    .select('*');

// Inside the component
const baseTransactionsQuery = useCallback(
  () => buildTransactionsQuery(category, month),
  [category, month]
);

51-67: Simplify table checks in sync-event listener

The use of reloadTransactions from the useTransactions hook is a good improvement. However, the table checks can be further simplified for better readability and maintainability.

Consider refactoring the table checks like this:

useEffect(() => {
  const transactionTables = ['transactions', 'category_mapping', 'payee_mapping'];
  const payeeTables = ['payees', 'payee_mapping'];

  return listen('sync-event', ({ type, tables }) => {
    if (type === 'applied') {
      if (tables.some(table => transactionTables.includes(table))) {
        reloadTransactions?.();
      }
      if (tables.some(table => payeeTables.includes(table))) {
        dispatch(getPayees());
      }
    }
  });
}, [dispatch, reloadTransactions]);

This refactoring reduces duplication and improves readability by grouping related tables and using Array.some() for checks.

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

7-8: Improved type safety and import structure.

The addition of new imports and type definitions enhances type safety and component structure. The change from getAccountFilter to accountFilter aligns with refactoring in the queries module.

Consider grouping related imports together for better readability. For example:

import React, { createContext, useContext, useEffect, useState, useRef, type PropsWithChildren } from 'react';
import { useSyncedPref } from '@actual-app/web/src/hooks/useSyncedPref';
import { q, type Query } from '../../shared/query';
import { getHasTransactionsQuery, getStatus } from '../../shared/schedules';
import { type TransactionEntity, type ScheduleEntity, type AccountEntity } from '../../types/models';
import { accountFilter } from '../queries';
import { type LiveQuery, liveQuery } from '../query-helpers';

Also applies to: 15-20


26-50: Enhanced error handling and type safety in loadStatuses.

The changes to the loadStatuses function improve error handling, flexibility, and type safety. The addition of onError parameter and explicit typing of liveQuery return value are particularly beneficial.

Consider using a more specific type for upcomingLength:

function loadStatuses(
  schedules: readonly ScheduleEntity[],
  onData: (data: ScheduleStatuses) => void,
  onError: (error: Error) => void,
  upcomingLength: number,
) {
  // ...
}

This change would ensure that upcomingLength is always a number, preventing potential type-related issues.


66-123: Significant improvements to useSchedules hook.

The changes to the useSchedules hook greatly enhance its functionality, error handling, and state management. The use of useRef for managing live queries and the updated useEffect hook provide more robust loading state management and error handling.

Consider extracting the query validation logic into a separate function for better readability:

const validateQuery = (query: Query | undefined): Error | null => {
  if (!query) {
    return null;
  }
  if (query.state.table !== 'schedules') {
    return new Error('Query must be a schedules query.');
  }
  return null;
};

// In the useEffect:
const queryError = validateQuery(query);
if (queryError) {
  setError(queryError);
  return;
}

This change would make the useEffect hook more concise and easier to read.


151-173: New accountSchedulesQuery function improves query generation.

The addition of the accountSchedulesQuery function provides a centralized and flexible way to generate account schedule queries. It handles various account types and uses the accountFilter function effectively for both account and payee filtering.

Consider using a TypeScript union type for the accountId parameter to improve type safety:

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

export function accountSchedulesQuery(accountId?: AccountIdType) {
  // ...
}

This change would make the function signature more explicit about the allowed values for accountId.

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

32-93: Well-implemented useTransactions hook with room for minor improvement

The useTransactions hook is well-structured and efficiently manages loading, error, and transaction states. The use of refs for the paged query and options is a good optimization to prevent unnecessary re-renders.

One suggestion for improvement:

Consider resetting the transactions state when an error occurs. This ensures that the UI doesn't display stale data in case of an error.

Apply this diff to reset transactions when an error occurs:

 onError: error => {
   if (!isUnmounted) {
     setError(error);
+    setTransactions([]);
   }
 },

101-186: Well-implemented usePreviewTransactions hook with room for optimization

The usePreviewTransactions hook effectively manages preview transactions, loading states, and errors. The use of useMemo for scheduleTransactions is a good optimization.

Two suggestions for improvement:

  1. Optimize the effect's dependency array:
    The current dependency array includes schedules and statuses, which are already used in the useMemo hook for scheduleTransactions. Consider removing them from the effect's dependency array to prevent unnecessary effect runs.

  2. Enhance error handling:
    Consider resetting the previewTransactions state when an error occurs to ensure the UI doesn't display stale data.

Apply these diffs for the suggested improvements:

  1. Optimize effect dependency array:
-  }, [scheduleTransactions, schedules, statuses]);
+  }, [scheduleTransactions]);
  1. Enhance error handling:
 .catch(error => {
   if (!isUnmounted) {
     setError(error);
+    setPreviewTransactions([]);
   }
 });

200-228: Well-implemented useTransactionsSearch hook with minor improvement opportunity

The useTransactionsSearch hook effectively manages search functionality for transactions. The use of debounce is good for performance, and the implementation of useMemo for the debounced function is appropriate.

One suggestion for improvement:

The conditional logic in the debounced function can be simplified. The else if (searchText) condition is redundant because if searchText is not an empty string, it will always be truthy.

Apply this diff to simplify the conditional logic:

 debounce((searchText: string) => {
   if (searchText === '') {
     resetQuery();
     setIsSearching(false);
-  } else if (searchText) {
+  } else {
     updateQuery(previousQuery =>
       queries.transactionsSearch(previousQuery, searchText, dateFormat),
     );
     setIsSearching(true);
   }
 }, delayMs),
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)

59-62: Improved query handling for schedules

The replacement of useDefaultSchedulesQueryTransform with useMemo and accountSchedulesQuery is a good improvement. It simplifies the component and aligns it with the new query structure.

Consider adding a brief comment explaining the purpose of schedulesQuery for better code readability:

 const schedulesQuery = useMemo(
   () => accountSchedulesQuery(accountId),
   [accountId],
 );
+// schedulesQuery is used to fetch and manage account-specific schedules

Also applies to: 81-87


Line range hint 106-155: Effective use of useCallback for performance optimization

The refactoring of onSave, onSaveNotes, onEditNotes, onCloseAccount, onReopenAccount, and onClick to use useCallback is a great optimization. This ensures stable function references across re-renders, potentially improving performance.

For consistency, consider also wrapping onSaveNotes with useCallback:

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

This maintains a consistent pattern across all callback functions in this component.


224-318: Significant improvement in transaction management

The introduction of useTransactions, usePreviewTransactions, and useTransactionsSearch hooks, along with the new state management for transactionsQuery, greatly improves the transaction handling logic. This approach simplifies the component and likely enhances performance.

Consider adding error handling for the case where transactions or previewTransactions might be undefined:

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

This ensures that the component doesn't crash if either previewTransactions or transactions is unexpectedly undefined.

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

10-22: LGTM: Enhanced liveQuery function with improved type safety

The refactored liveQuery function now uses generics and has a more structured parameter list, improving type safety and flexibility. The use of an options object allows for easier future extensions.

Consider adding a default type for TResponse in the options object to match the function's default type:

 export function liveQuery<TResponse = unknown>(
   query: Query,
   {
     onData,
     onError,
     options = {},
-  }: {
+  }: {
+    onData?: Listener<TResponse>;
+    onError?: (error: Error) => void;
+    options?: LiveQueryOptions;
+  } = {}
 ): LiveQuery<TResponse> {
   return LiveQuery.runLiveQuery<TResponse>(query, onData, onError, options);
 }

This change would allow calling liveQuery without any parameters other than query.


25-45: LGTM: Enhanced pagedQuery function with improved type safety and new functionality

The refactored pagedQuery function now uses generics, has a more structured parameter list, and includes a new onPageData callback. These changes improve type safety and add functionality for handling page data separately.

Similar to the liveQuery function, consider adding a default type for the options object:

 export function pagedQuery<TResponse = unknown>(
   query: Query,
   {
     onData,
     onError,
     onPageData,
     options = {},
-  }: {
+  }: {
+    onData?: Listener<TResponse>;
+    onError?: (error: Error) => void;
+    onPageData?: (data: Data<TResponse>) => void;
+    options?: PagedQueryOptions;
+  } = {}
 ): PagedQuery<TResponse> {
   return PagedQuery.runPagedQuery<TResponse>(
     query,
     onData,
     onError,
     onPageData,
     options,
   );
 }

This change would allow calling pagedQuery without any parameters other than query.


48-160: LGTM: Improved type definitions and LiveQuery class enhancements

The new type definitions (Data, Listener, and LiveQueryOptions) and the updates to the LiveQuery class significantly improve type safety and readability. The addition of the static runLiveQuery method simplifies the creation and execution of live queries.

Consider updating the type of _unsubscribeSyncEvent to improve clarity:

- private _unsubscribeSyncEvent: () => void | null;
+ private _unsubscribeSyncEvent: (() => void) | undefined;

This change aligns with TypeScript best practices for optional function types and matches the initialization in the constructor.

🧰 Tools
🪛 Biome

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)


Line range hint 242-426: LGTM: Enhanced PagedQuery class with improved paging functionality

The PagedQuery class now extends LiveQuery<TResponse>, improving type consistency. The additions and refactoring enhance the paging functionality, particularly the fetchNext method, which now handles concurrent requests more robustly.

Consider enhancing the error handling in the _fetchNext method:

 private _fetchNext = async () => {
   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),
-    );
+    try {
+      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) {
-      return this._fetchNext();
-    } else {
-      if (data.length === 0) {
-        this._hasReachedEnd = true;
-      } else {
-        this._hasReachedEnd = data.length < this._pageCount;
+      // 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) {
+        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));
+          const prevData = this.data;
+          this.updateData(currentData => currentData.concat(data));

-        // Handle newly loaded page data
-        this.onPageData(data);
+          // Handle newly loaded page data
+          this.onPageData(data);

-        // Handle entire data
-        this.onData(this.data, prevData);
+          // Handle entire data
+          this.onData(this.data, prevData);
+        }
+      }
+    } catch (error) {
+      console.error('Error fetching next page:', error);
+      this.onError(error);
     }
   }
 };

This change adds proper error handling to the _fetchNext method, ensuring that any errors during the query execution are caught and properly reported.

🧰 Tools
🪛 Biome

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0c4900d and 6ef8166.

📒 Files selected for processing (13)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Account.tsx (15 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/ScheduledTransactionMenuModal.tsx (3 hunks)
  • packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/index.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)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/desktop-client/src/components/ManageRules.tsx
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx
🧰 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-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/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/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 (26)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (3)

1-4: LGTM: Import statements updated correctly.

The changes to the import statements are consistent with the PR objectives. The addition of useMemo and q imports, along with the updated source for useSchedules, align well with the subsequent changes in the component.


Line range hint 28-41: LGTM: Consistent data handling in Value component.

The update to use schedules directly in the data prop of the Value component is correct. This change, combined with the default empty array in the destructuring, ensures that data always receives an array, preventing potential issues.


Line range hint 1-41: Summary: Successful refactoring of ScheduleValue component.

The changes in this file successfully implement the new useSchedules hook, improving data fetching and state management. The modifications align well with the PR objectives and contribute to the overall goal of simplifying transaction loading. The component now handles loading states more effectively and uses React hooks efficiently.

Key improvements:

  1. Updated import statements for consistency with the new hook location.
  2. Efficient use of useMemo for query optimization.
  3. Improved loading state handling.
  4. Consistent data flow to the Value component.

These changes enhance the component's robustness and maintainability.

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

2-2: LGTM: Import additions enhance functionality.

The addition of useMemo and q imports are appropriate for the upcoming changes in the component. These imports will support performance optimization and improved data querying.

Also applies to: 9-9


43-45: LGTM: Improved loading state handling.

The use of isSchedulesLoading to conditionally render null enhances the component's robustness during data fetching. This prevents potential errors from rendering incomplete data.


Line range hint 82-89: LGTM: Improved type definition for ScheduledTransactionMenuProps.

The updated type definition using Omit provides a more precise and maintainable way to define the component's props. This change ensures type safety and prevents passing incorrect props to the ScheduledTransactionMenu component.


Line range hint 1-124: Overall, excellent refactoring to improve data management and component safety.

The changes in this file align well with the PR objectives of simplifying transaction loading. Key improvements include:

  1. Enhanced data fetching with the updated useSchedules hook.
  2. Improved loading state management.
  3. Safer data access using optional chaining.
  4. More precise type definitions.

These changes contribute to a more robust and maintainable component. The refactoring is consistent with the broader changes mentioned in the AI summary, particularly in how the useSchedules hook is used across the application.

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

5-8: LGTM: New hooks for simplified transaction management

The addition of useTransactions and useTransactionsSearch hooks aligns with the PR objective of simplifying transaction loading. This change should lead to more maintainable and reusable code for handling transactions.


37-47: LGTM: Simplified transaction management with useTransactions hook

The use of the useTransactions hook greatly simplifies the component by centralizing transaction state management and loading logic. This change aligns perfectly with the PR objective and should make the code more maintainable.


69-82: LGTM: Improved search functionality and transaction opening

The use of the useTransactionsSearch hook simplifies the search functionality, while the useCallback for onOpenTransaction is a good performance optimization. These changes contribute to the overall goal of simplifying transaction management.


Line range hint 1-128: Overall assessment: Significant improvements in transaction management

The changes in this file successfully implement the PR objectives by introducing the useTransactions and useTransactionsSearch hooks. These modifications have simplified the CategoryTransactions component, making it more maintainable and aligned with modern React practices. The use of custom hooks for transaction management and search functionality is a notable improvement.

A few minor suggestions for further refinement have been provided, but overall, this is a well-executed refactoring that should enhance the codebase's quality and maintainability.

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

54-64: Improved type definitions for useSchedules hook.

The addition of UseSchedulesProps, ScheduleData, and UseSchedulesResult types significantly enhances the type safety and readability of the useSchedules hook. These types provide a clear structure for the hook's input, internal state, and output.


Line range hint 128-145: Improved SchedulesContext and SchedulesProvider.

The updates to SchedulesContext and SchedulesProvider enhance the consistency and usability of the scheduling functionality. The default values in SchedulesContext ensure that consumers have a consistent initial state, and the changes to SchedulesProvider align it with the new useSchedules hook structure.


Line range hint 1-173: Overall improvements to schedules management.

The changes in this file significantly enhance the management of schedules, aligning well with the PR objectives of simplifying transaction loading and previewing. Key improvements include:

  1. Enhanced type safety throughout the file.
  2. Improved error handling in the useSchedules hook.
  3. More efficient query management using useRef for live queries.
  4. A new accountSchedulesQuery function for centralized query generation.
  5. Updated SchedulesContext and SchedulesProvider for better state management.

These changes provide a solid foundation for managing schedules across the application, which should facilitate the implementation of the useTransactions hook in other parts of the codebase.

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

230-236: Well-implemented isForPreview utility function

The isForPreview function effectively determines if a schedule is eligible for preview based on its completion status and the status from ScheduleStatuses. The logic is clear and concise.

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

21-28: Excellent use of custom hooks for improved modularity

The introduction of accountSchedulesQuery, SchedulesProvider, and the new transaction-related hooks (usePreviewTransactions, useTransactions, useTransactionsSearch) is a great improvement. This approach enhances code modularity, reusability, and maintainability by encapsulating complex logic within custom hooks.


249-253: Addressed: Prevent marking account as read when accountId is undefined

The suggested improvement has been implemented. The onRefresh function now checks if accountId is defined before dispatching the sync action. This prevents potential issues when accountId is undefined.


255-259: Addressed: Handle undefined accountId in markAccountRead

The suggested improvement has been implemented. The useEffect hook now checks if accountId is defined before dispatching the markAccountRead action. This prevents potential issues when accountId is undefined.


Line range hint 1-365: Overall excellent improvements with minor suggestions for refinement

The changes made to AccountTransactions.tsx represent a significant improvement in code organization, performance optimization, and functionality. The introduction of custom hooks for transaction and schedule management, along with the consistent use of useCallback for memoization, are particularly commendable.

Key improvements:

  1. Enhanced modularity through custom hooks
  2. Improved performance with useCallback and memoization
  3. Better state management for transactions
  4. Simplified and more consistent query handling for schedules

Areas for further refinement:

  1. Add brief comments explaining the purpose of key variables or functions
  2. Implement suggested error handling for transactionId splitting
  3. Optimize the sync event listener as suggested

These changes have greatly enhanced the maintainability and performance of the component. Great work overall!

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

4-4: LGTM: Improved type safety with Query import

The addition of the Query type import enhances type safety for the file, providing better type checking for query-related operations.


Line range hint 1-426: Overall: Excellent improvements to query helpers

The changes in this file significantly enhance type safety, readability, and functionality of the query helpers. The introduction of generics, new type definitions, and the refactoring of LiveQuery and PagedQuery classes align well with the PR objectives of simplifying transaction loading and improving the overall structure.

The new liveQuery and pagedQuery functions provide a more flexible and type-safe API for consumers. The enhancements to error handling and the introduction of the onPageData callback in the PagedQuery class offer improved control over data loading and error management.

These changes lay a solid foundation for the implementation of the useTransactions hook mentioned in the PR objectives. Great work on improving the core query functionality!

🧰 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/schedules/index.tsx (1)

79-80: 🛠️ Refactor suggestion

Display a loading indicator while schedules are loading

Returning null when isSchedulesLoading is true may result in an empty screen, which could confuse users. Consider displaying a loading indicator to enhance the user experience.

Apply this diff to show a loading message:

 if (isSchedulesLoading) {
-  return null;
+  return <div>Loading schedules...</div>;
 }

Alternatively, you can use a spinner or a dedicated loading component for better visual feedback.

Likely invalid or redundant comment.

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

25-29: Imports from 'query-helpers' are correctly added

The necessary functions and types (runQuery, pagedQuery, PagedQuery) are imported from 'loot-core/src/client/query-helpers', facilitating pagination and query operations within the component.


329-329: Type of paged updated to enhance type safety

Explicitly defining paged as PagedQuery<TransactionEntity> | null improves type safety and clarifies the expected type of the paged property within the AccountInternal class.


489-492: Renaming makeRootQuery to makeRootTransactionsQuery improves clarity

The method makeRootQuery has been renamed to makeRootTransactionsQuery, which better reflects its purpose of creating the root query for transactions.


655-665: Optimistic updates to paged are correctly implemented

The onTransactionsChange method properly uses this.paged?.optimisticUpdate to update the local transaction data, ensuring that changes are reflected promptly in the UI.

Copy link
Contributor

@jfdoming jfdoming left a comment

Choose a reason for hiding this comment

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

I noticed there's some flickering on the schedules page now that isn't present in the previous release. To reproduce, open the devtools, and set a 20x slowdown on the "Application" tab, then switch away and back to the Schedules page (e.g. Schedules -> Payees -> Schedules). I think it would be good to at least display a spinner, and ideally fix the regression from the previous behaviour. Any ideas what might be causing this?

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

return useMemo(() => sort(filters), [filters]);
return useMemo(() => sort(toJS(data ? [...data] : [])), [data, sort]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the callback is just used once, can we just inline it instead and avoid a useCallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated :)

Comment on lines 37 to 38
query: useMemo(
() => q('schedules').filter({ id: scheduleId }).select('*'),
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: it's nice to have hooks at the top level so we don't accidentally make them conditional later

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. Updated :)

@joel-jeremy
Copy link
Contributor Author

I noticed there's some flickering on the schedules page now that isn't present in the previous release. To reproduce, open the devtools, and set a 20x slowdown on the "Application" tab, then switch away and back to the Schedules page (e.g. Schedules -> Payees -> Schedules). I think it would be good to at least display a spinner, and ideally fix the regression from the previous behaviour. Any ideas what might be causing this?

Nice catch! Just needed to properly handle the loading states. It should now display loading indicators instead of not rendering the entire component.

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 (10)
packages/loot-core/src/client/query-hooks.ts (2)

17-21: Enhance memoization documentation.

Consider expanding the comments to better explain:

  1. Why ignoring function identity changes is safe
  2. The specific eslint rule being disabled and its implications
- // Memo the resulting query. We don't care if the function
- // that creates the query changes, only the resulting query.
- // Safe to ignore the eslint warning here.
+ // Memoize the query result, not the function that creates it.
+ // This is safe because we only care about the final Query object,
+ // not the intermediate function's identity. The eslint warning
+ // about missing function dependency can be safely ignored as
+ // we intentionally want to respond only to query changes.

27-50: Consider enhancing the null query error message.

While the error handling is robust, the error message could be more descriptive to aid debugging.

-    setError(query === null ? new Error('Query is null') : undefined);
+    setError(
+      query === null
+        ? new Error('Query cannot be null - ensure makeQuery returns a valid Query object')
+        : undefined
+    );
packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2)

Line range hint 391-401: Consider enhancing error handling with more specific messages

While the validation for duplicate schedule names is good, the generic error message for save failures could be more helpful by including the specific error details from the response.

Consider updating the error handling:

     if (res.error) {
       dispatch({
         type: 'form-error',
-        error: t(
-          'An error occurred while saving. Please visit https://actualbudget.org/contact/ for support.',
-        ),
+        error: t('Failed to save schedule: {{error}}', { error: res.error }),
       });
       return;
     }

Line range hint 467-469: Add loading indicator to prevent flickering

The current implementation returns null while loading, which could contribute to the flickering issues mentioned in the PR objectives. Consider adding a loading indicator to provide better visual feedback.

Consider implementing a loading state:

   if (state.schedule == null) {
-    return null;
+    return (
+      <View style={{ padding: 20, textAlign: 'center' }}>
+        <Text>Loading schedule details...</Text>
+      </View>
+    );
   }
packages/loot-core/src/shared/query.ts (6)

Line range hint 90-95: Simplify Parameter Types in 'groupBy' Method

The groupBy method accepts multiple types for exprs. To improve readability and maintainability, consider overloading the method or simplifying the parameter types.


Line range hint 101-106: Refine Parameter Types in 'orderBy' Method

Similar to groupBy, the orderBy method's exprs parameter accepts various types. Refactoring the method to use method overloading or simplifying the types can enhance code clarity.


112-112: Validate Input in 'limit' Method

The limit method requires a number as input. Consider adding validation to ensure that num is a positive integer to prevent potential runtime errors or unexpected behavior.


116-116: Add Validation to 'offset' Method

Similarly, the offset method should validate that num is a non-negative integer. This ensures robustness and prevents issues during query execution.


132-132: Consider Merging Options in 'options' Method

The options method accepts a Record<string, unknown>. If certain options are frequently used together, consider merging the new options with existing ones to maintain previous configurations unless explicitly overridden.


145-148: Enhance Type Safety in 'getPrimaryOrderBy' Function

The getPrimaryOrderBy function accepts defaultOrderBy: ObjectExpression | null. Ensure that all possible return types are adequately handled and consider defining a specific return type for clarity and improved maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7e8f3ad and 5ed9bf7.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
📒 Files selected for processing (35)
  • .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/Account.tsx (15 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (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/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 (4 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1 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/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)
💤 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 (22)
  • .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/Balance.jsx
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.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/ScheduleLink.tsx
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx
  • 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/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
🧰 Additional context used
📓 Learnings (5)
packages/desktop-client/src/components/accounts/Account.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.
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: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.
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.
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/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/loot-core/src/client/query-hooks.ts (4)

7-11: Well-structured type definition with immutability!

The UseQueryResult type effectively encapsulates query state with immutable data using ReadonlyArray.


13-16: Improved type safety and parameter naming!

The signature changes enhance type safety by explicitly handling null queries and use more descriptive parameter naming with 'dependencies'.


23-25: Robust state initialization with proper typing!

The state initialization addresses previous concerns by:

  • Correctly initializing isLoading based on query presence
  • Using proper TypeScript types for immutable data
  • Including a well-typed error state

52-57: Clean and type-safe return value!

The return value properly implements the UseQueryResult type, providing all necessary state information.

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

1-8: LGTM: Clean import organization

The imports are well-organized and include all necessary dependencies for the new Redux-based implementation.


71-76: LGTM: Efficient query implementation

The query is properly memoized and the loading state is correctly destructured from the useSchedules hook. This aligns with the PR's objective of simplifying transaction loading.


41-69: ⚠️ Potential issue

Add error handling for async operations

The onAction callback performs multiple async operations without try-catch blocks. While it handles unknown actions, it doesn't handle potential network or server errors.

Add error handling:

 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) {
+      console.error(`Failed to perform ${name} action on schedule ${id}:`, error);
+      // Consider showing a user-friendly error message
+      throw error; // Re-throw to allow parent components to handle the error
+    }
   },
   [],
 );

Likely invalid or redundant comment.


112-112: Verify loading state fixes flickering

The addition of isLoading prop should address the flickering issue mentioned in the PR comments. Please verify this fix using the following steps:

  1. Use developer tools to set a 20x slowdown
  2. Switch away from and back to the Schedules page

Let's verify the loading state implementation:

✅ Verification successful

Based on the search results from all the scripts, I can see that:

  1. The SchedulesTable component properly accepts isLoading as a prop in its type definition (SchedulesTableProps)
  2. The loading state is properly passed from the parent component to SchedulesTable
  3. The loading state comes from useSchedules hook which manages the loading state during data fetching

Since the loading state is properly implemented in the component hierarchy and is tied to actual data fetching, this should effectively prevent flickering by showing a loading state while data is being fetched.

Loading state implementation verified

The loading state implementation follows best practices by:

  • Being properly typed in component props
  • Coming from a data fetching hook
  • Being passed through the component hierarchy
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if SchedulesTable properly handles the loading state
# Expected: Find loading state handling in SchedulesTable component

# Look for loading state handling in SchedulesTable
rg -A 5 'isLoading.*prop|loading.*state' --type typescript

Length of output: 95


Script:

#!/bin/bash
# Let's try different approaches to find loading state handling

# Search for isLoading in all files
rg -A 5 "isLoading"

# Search for SchedulesTable component definition
ast-grep --pattern 'const SchedulesTable = $_'

Length of output: 58374


Script:

#!/bin/bash
# Let's look for the SchedulesTable component implementation
rg -A 10 "SchedulesTable.*Props" --type tsx

# Also search for loading state handling in the component
rg -A 10 "loading.*={.*isSchedulesLoading" --type tsx

Length of output: 254


Script:

#!/bin/bash
# Let's try simpler searches without file type restrictions
# Look for SchedulesTable component and loading state handling
rg -A 10 "SchedulesTable.*Props"

# Search for the loading prop usage in the component
rg -A 5 "loading={isSchedulesLoading"

Length of output: 3407


Script:

#!/bin/bash
# Search for loading state handling in SchedulesTable component
rg -A 5 "loading.*prop" packages/desktop-client/src/components/schedules/SchedulesTable.tsx

# Look for the actual table rendering with loading state
rg -A 5 "Table.*loading" packages/desktop-client/src/components/schedules/SchedulesTable.tsx

Length of output: 188

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

15-20: LGTM! Well-structured type imports and model definitions.

The addition of proper TypeScript types and model entities improves type safety and code maintainability.


26-31: LGTM! Improved error handling with onError callback.

The function signature improvements with readonly types and explicit error handling enhance the robustness of the code.


77-78: LGTM! Proper query cleanup implementation.

The use of refs for storing query references and proper cleanup in the effect's return function prevents memory leaks and ensures proper resource management.

Also applies to: 121-123


Line range hint 140-159: LGTM! Well-implemented context with proper TypeScript types.

The SchedulesProvider and useCachedSchedules hook follow React context best practices with descriptive error messages.


163-165: Consider adding input validation for accountId.

While the function handles various account types, it might benefit from explicit validation of accountId values to prevent potential runtime errors with invalid inputs.

Consider adding type validation:

function isValidAccountId(id: string): id is AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized' {
  return ['budgeted', 'offbudget', 'uncategorized'].includes(id) || 
         // Add validation for AccountEntity['id'] format
         /^[a-zA-Z0-9-_]+$/.test(id);
}
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (9)

21-28: LGTM! Clean import organization

The imports are well-organized, with related hooks grouped together. The new transaction hooks (usePreviewTransactions, useTransactions, useTransactionsSearch) are properly imported from the data-hooks directory.


59-62: LGTM! Proper memoization of schedules query

The schedulesQuery is correctly memoized using useMemo with accountId as its only dependency.


Line range hint 81-87: LGTM! Clean component composition

The SchedulesProvider is properly configured with the memoized query, and the TransactionListWithPreviews receives all necessary props.


217-222: LGTM! Improved type safety for accountId

The type definition for accountId has been expanded to include specific string literals ('budgeted', 'offbudget', 'uncategorized') alongside the AccountEntity id type, improving type safety.


249-259: LGTM! Proper null checks for accountId

Both onRefresh and the markAccountRead effect properly check for accountId before dispatching actions.


279-284: LGTM! Clean search implementation

The search implementation using useTransactionsSearch is well-structured with proper query management.


293-302: Add validation for transactionId splitting

The existing comment about adding validation for transactionId splitting in the onPost and onSkip handlers is still valid. Consider implementing the suggested error handling to prevent potential runtime errors.


315-318: LGTM! Clean transaction display logic

The transaction display logic correctly combines preview and regular transactions based on the search state.


321-322: LGTM! Proper loading state handling

The loading state correctly accounts for both regular transactions and preview transactions loading states.

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

Line range hint 383-417: LGTM: Robust implementation of fetchNext

The implementation properly handles:

  • Race conditions with in-flight requests
  • Data consistency checks
  • Pagination boundaries
  • Event notifications

103-104: ⚠️ Potential issue

Initialize properties according to their types

The properties _data and _dependencies are initialized to null, but their types are Data<TResponse> and Set<string> respectively.

-    this._data = null;
-    this._dependencies = null;
+    this._data = [];
+    this._dependencies = new Set<string>();

Likely invalid or redundant comment.


290-290: ⚠️ Potential issue

Await the asynchronous fetchCount() method

The fetchCount() method is asynchronous, but it's called without awaiting its completion.

-      this.fetchCount();
+      await this.fetchCount();

Likely invalid or redundant comment.


422-425: ⚠️ Potential issue

Validate totalCount updates in optimisticUpdate

The optimisticUpdate method modifies _totalCount without validation, which could lead to inconsistent state.

   optimisticUpdate = (updateFn: (data: Data<TResponse>) => Data<TResponse>) => {
     const previousData = this.data;
     this._optimisticUpdate(updateFn);
-    this._totalCount += this.data.length - previousData.length;
+    const diff = this.data.length - previousData.length;
+    if (this._totalCount + diff >= 0) {
+      this._totalCount += diff;
+    } else {
+      console.warn('Invalid optimistic update: total count would become negative');
+      // Revert the update
+      this.data = previousData;
+    }
   };

Likely invalid or redundant comment.


10-21: 🛠️ Refactor suggestion

Consider adding type constraints to TResponse

The generic type parameter TResponse could benefit from type constraints to ensure type safety.

-export function liveQuery<TResponse = unknown>(
+export function liveQuery<TResponse extends Record<string, unknown> = Record<string, unknown>>(

Likely invalid or redundant comment.


61-61: ⚠️ Potential issue

Improve type clarity for _unsubscribeSyncEvent

The current type uses void in a union type, which can be confusing.

-  private _unsubscribeSyncEvent: () => void | null;
+  private _unsubscribeSyncEvent: (() => void) | undefined;

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)

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

140-140: LGTM: Improved parameter structure

The update to use an object parameter with named properties (onData) improves code readability and maintainability.


156-159: LGTM: Clear separation of callback and options

The object parameter structure clearly separates the data callback from configuration options, making the code more organized.


174-177: LGTM: Consistent parameter structure across test cases

The parameter structure has been consistently updated across all test cases, maintaining clear separation between callbacks and options while preserving the test scenarios' intentions.

Also applies to: 200-202, 230-233, 254-257, 279-281


313-313: LGTM: Improved state access API

The change from method calls to property access (e.g., paged.totalCount, paged.hasNext, paged.data) provides a more intuitive API for accessing query state.

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


361-363: LGTM: Clear configuration of page count

Moving pageCount into the options object provides a more organized way to configure the paged query behavior.


378-380: LGTM: Maintained concurrent fetch test integrity

The parameter structure update preserves the critical test case that ensures proper handling of concurrent fetch operations.


404-407: LGTM: Comprehensive test coverage maintained

The parameter structure has been consistently updated while preserving the comprehensive test coverage for various paged query behaviors including refetching, data updates, and specific row fetching.

Also applies to: 440-443, 484-487

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

301-304: LGTM: Improved live query implementation

The refactoring of liveQuery calls to use explicit onData callbacks enhances code clarity and maintainability.

Also applies to: 344-347

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

329-329: LGTM! Type safety improvement

The explicit PagedQuery type improves type safety by properly constraining the generic type parameter.


1881-1884: ⚠️ Potential issue

Handle potential undefined params.id

The accountSchedulesQuery may receive undefined params.id which could cause runtime issues.

Apply this diff to handle undefined case:

  const schedulesQuery = useMemo(
-   () => accountSchedulesQuery(params.id),
+   () => accountSchedulesQuery(params.id || ''),
    [params.id],
  );

Likely invalid or redundant comment.

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

770-777: LGTM! The loading state handling improves component stability.

The addition of loading state check prevents rendering schedule-related UI elements until the data is ready, which helps avoid potential null reference errors and UI flickering. This is a good practice for handling asynchronous data in React components.

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

3-5: Ensure TypeScript Handles Recursive 'ObjectExpression' Type Effectively

The ObjectExpression type is recursively defined, which can increase the complexity of type checking in TypeScript. Verify that this definition does not negatively impact the compilation time or introduce type inference issues.


8-13: Confirm Updated 'QueryState' Types Are Consistent Across Codebase

The properties in QueryState have been updated to accept more complex types, such as ObjectExpression and arrays of mixed types. Ensure that all usages of QueryState throughout the codebase are compatible with these changes to prevent type errors.


25-25: Constructor Now Enforces Presence of 'table' Property

The constructor now requires the table property by using WithRequired<Partial<QueryState>, 'table'>. This change enhances type safety by ensuring that every Query instance is associated with a table.


42-44: 'filter' Method Now Accepts Complex Expressions

The filter method's parameter expr now accepts an ObjectExpression, allowing for more complex filtering criteria. Verify that all existing calls to filter pass the correct expression types and that they function as intended.

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

32-97: Well-implemented useTransactions hook

The useTransactions hook is well-structured and effectively manages data fetching, pagination, and error handling.


105-191: Solid implementation of usePreviewTransactions hook

The usePreviewTransactions hook efficiently handles the preview of transactions with appropriate loading and error state management.


213-227: Consider using useDebounce hook for better integration with React

A previous review comment suggesting the use of the useDebounce hook from the use-debounce package instead of lodash/debounce is still applicable. This change can improve consistency with React's hook patterns and simplify the code.

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 the base query function

While the implementation is correct, the baseTransactionsQuery callback could be memoized to prevent unnecessary recreations.

Apply this diff to optimize the query creation:

- const baseTransactionsQuery = useCallback(
+ const baseTransactionsQuery = useMemo(
    () =>
-     queries.transactions(accountId).options({ splits: 'none' }).select('*'),
+     () => queries.transactions(accountId).options({ splits: 'none' }).select('*'),
    [accountId],
  );

Committable suggestion was skipped due to low confidence.

Comment on lines +214 to +234
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;
}
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 request cancellation cleanup

The request ID tracking logic could leak memory if the query is unsubscribed during an in-flight request.

   try {
     const { data, dependencies } = await runQuery();
+    // Clean up if unsubscribed during request
+    if (!this._unsubscribeSyncEvent) {
+      this.inflightRequestId = null;
+      return;
+    }
     
     if (this._dependencies == null) {
       this._dependencies = new Set(dependencies);
     }
📝 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 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;
}
const reqId = Math.random();
this.inflightRequestId = reqId;
try {
const { data, dependencies } = await runQuery();
// Clean up if unsubscribed during request
if (!this._unsubscribeSyncEvent) {
this.inflightRequestId = null;
return;
}
// 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;
}

@@ -133,6 +163,6 @@ export function getPrimaryOrderBy(query, defaultOrderBy) {
return { field, order: firstOrder[field] };
}

export function q(table) {
export function q(table: QueryState['table']) {
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 Parameter Type in 'q' Function

The q function currently takes table: QueryState['table'], which resolves to string. For simplicity and readability, consider changing the parameter type directly to string unless there is a specific reason to tie it to QueryState.

Apply this diff to simplify the parameter type:

-export function q(table: QueryState['table']) {
+export function q(table: string) {
📝 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 q(table: QueryState['table']) {
export function q(table: string) {

@@ -0,0 +1,245 @@
import { useEffect, useRef, useState, useMemo } from 'react';

import debounce from 'lodash/debounce';
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

Replace lodash/debounce with use-debounce hook

Switching to the useDebounce hook from the use-debounce package can enhance integration with React hooks and reduce complexity in cleanup.

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 (10)
packages/loot-core/src/client/data-hooks/schedules.tsx (1)

163-185: Consider adding JSDoc documentation.

This function handles complex query building logic with multiple account filter cases. Adding JSDoc documentation would help explain:

  • The purpose of each filter condition
  • The meaning of special accountId values ('budgeted', 'offbudget', 'uncategorized')
  • The expected query results for each case

Example documentation:

/**
 * Creates a query to fetch schedules filtered by account.
 * @param accountId - The account to filter by. Special values:
 *   - 'budgeted': Returns schedules for budgeted accounts
 *   - 'offbudget': Returns schedules for off-budget accounts
 *   - 'uncategorized': Returns schedules with no next date
 * @returns A query object to fetch filtered schedules
 */
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.

 const onSaveNotes = useCallback(async (id: string, notes: string) => {
-  await send('notes-save', { id, note: notes });
+  try {
+    await send('notes-save', { id, note: notes });
+  } catch (error) {
+    console.error('Failed to save notes:', error);
+    // Consider showing a user-friendly error message
+  }
 }, []);
packages/loot-core/src/client/query-helpers.ts (1)

Line range hint 383-417: Consider adding retry limit to _fetchNext

The _fetchNext method could potentially enter an infinite loop if the data keeps changing or if requests keep getting cancelled.

Add a retry limit to prevent infinite loops:

   private _fetchNext = async () => {
+    let retries = 0;
+    const MAX_RETRIES = 3;
     while (this.inflightRequestId) {
+      if (retries >= MAX_RETRIES) {
+        throw new Error('Max retries reached while fetching next page');
+      }
       await this._fetchDataPromise;
+      retries++;
     }
packages/loot-core/src/client/query-helpers.test.ts (1)

Line range hint 300-348: LGTM: Property access for pagination state

The change from method calls to property access (totalCount, hasNext, data) improves API ergonomics. Consider adding TypeScript interfaces to document these properties.

Consider adding JSDoc comments or TypeScript interfaces to document the available properties and their types:

interface PagedQueryResult<T> {
  /** Total number of items available */
  totalCount: number;
  /** Whether there are more items to fetch */
  hasNext: boolean;
  /** Currently loaded items */
  data: T[];
}
packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (5)

Line range hint 8-28: Consider consolidating mock setup for better maintainability.

The mock setup is spread across multiple locations. Consider:

  1. Moving all mock data (accounts, payees, categories) to a separate test fixtures file
  2. Grouping all vi.mock() calls together
+// test-fixtures/transaction-data.js
+export const mockAccounts = [generateAccount('Bank of America')];
+export const mockPayees = [
+  {
+    id: 'bob-id',
+    name: 'Bob',
+    favorite: 1,
+    transfer_acct: null,
+    category: null,
+  },
+  // ... other payees
+];
+
+// test-setup.js
+vi.mock('loot-core/src/platform/client/fetch');
+vi.mock('../../hooks/useFeatureFlag');
+vi.mock('../../hooks/useSyncedPref');

Line range hint 298-332: Add error handling for field queries.

The field query functions (queryNewField and queryField) don't handle cases where the field doesn't exist, which could lead to cryptic errors.

 function queryNewField(container, name, subSelector = '', idx = 0) {
   const field = container.querySelectorAll(
     `[data-testid="new-transaction"] [data-testid="${name}"]`,
   )[idx];
+  if (!field) {
+    throw new Error(`Field "${name}" not found in new transaction row ${idx}`);
+  }
   if (subSelector !== '') {
     return field.querySelector(subSelector);
   }
   return field;
 }

Line range hint 406-485: Consider using test.each for repetitive test cases.

The keyboard navigation tests repeat similar patterns. Consider using test.each to make the tests more maintainable.

+const keyboardTests = [
+  { key: 'Enter', shift: false, expectedMove: 'down' },
+  { key: 'Tab', shift: false, expectedMove: 'right' },
+  { key: 'Enter', shift: true, expectedMove: 'up' },
+  { key: 'Tab', shift: true, expectedMove: 'left' },
+];
+
+test.each(keyboardTests)(
+  'keybinding $key (shift: $shift) moves $expectedMove',
+  async ({ key, shift, expectedMove }) => {
+    const { container } = renderTransactions();
+    const input = await editField(container, 'notes', 2);
+    await userEvent.type(
+      input,
+      `${shift ? '{Shift>}' : ''}[${key}]${shift ? '{/Shift}' : ''}`,
+    );
+    // Assert based on expectedMove
+  },
+);

Line range hint 486-542: Fix skipped test for dropdown invalid value.

The test for dropdown invalid value reset is currently skipped. This should be fixed to ensure complete test coverage.

The comment indicates the test needs fixing. Consider:

  1. Breaking down the test into smaller, focused test cases
  2. Investigating the timing issues that might be causing flakiness
  3. Adding proper cleanup after each test case

Line range hint 543-1012: Add error scenarios to split transaction tests.

The split transaction tests cover the happy path but could benefit from additional error scenarios.

Consider adding tests for:

  1. Invalid split amounts (negative values)
  2. Split amounts exceeding the parent transaction
  3. Error handling when split operations fail
  4. Edge cases around decimal precision in splits
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)

770-774: Consider showing a loading indicator during schedule loading.

The current implementation returns null during loading, which is correct for preventing premature rendering. However, this might lead to a jarring user experience when schedules suddenly appear.

Consider adding a loading indicator:

  if (isLoading) {
-   return null;
+   return (
+     <View style={{ width: 23, height: 23 }}>
+       <LoadingSpinner size="small" />
+     </View>
+   );
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed9bf7 and 5a042b0.

⛔ Files ignored due to path filters (1)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
📒 Files selected for processing (36)
  • .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/Account.tsx (15 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (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/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 (4 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1 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/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)
💤 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)
  • .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/Balance.jsx
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.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/ScheduleLink.tsx
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx
  • packages/desktop-client/src/components/schedules/index.tsx
  • 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/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
🧰 Additional context used
📓 Learnings (4)
packages/desktop-client/src/components/accounts/Account.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.
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/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/loot-core/src/client/query-hooks.ts (6)

7-11: LGTM! Well-structured type definition.

The UseQueryResult type effectively standardizes query results with immutable data and proper error handling support.


14-16: LGTM! Improved type safety and clarity in hook signature.

The changes enhance type safety by:

  • Supporting null queries for better state handling
  • Using more descriptive parameter naming
  • Standardizing the return type

17-21: LGTM! Proper memoization with clear documentation.

The memoization strategy correctly focuses on the query result rather than the creation function, with well-documented ESLint override.


23-25: LGTM! Improved state initialization.

The state management has been enhanced with:

  • Proper typing for immutable data
  • Logical loading state based on query availability
  • Clear error state handling

This implementation addresses the previous review comment about initializing isLoading based on query availability.


52-57: LGTM! Consistent return value structure.

The return value properly implements the UseQueryResult type, providing all necessary states for consumers.


27-50: 🛠️ Refactor suggestion

Consider handling potential race conditions.

While the implementation correctly handles unmounting and error states, there's a potential race condition where responses from older queries could override newer ones if multiple queries are triggered in quick succession.

To verify this concern, let's check if there are any existing race condition issues:

Consider adding a query identifier to ensure only the latest query's response is processed:

 useEffect(() => {
   let isUnmounted = false;
+  const queryId = Math.random();
   let live: null | LiveQuery<Response> = liveQuery<Response>(query, {
     onData: data => {
-      if (!isUnmounted) {
+      if (!isUnmounted && queryId === currentQueryId.current) {
         setData(data);
         setIsLoading(false);
       }
     },
     onError: setError,
   });
packages/loot-core/src/client/data-hooks/schedules.tsx (4)

7-8: LGTM! Type safety improvements and better module organization.

The changes improve type safety through explicit type imports and enhance code organization by using the reusable accountFilter utility.

Also applies to: 15-21


54-131: LGTM! Robust state management and cleanup.

The implementation properly manages loading and error states, with comprehensive cleanup of subscriptions. The error state is appropriately cleared when the query changes.


Line range hint 140-162: LGTM! Well-structured Context implementation.

The Context and Provider implementation follows React best practices with proper TypeScript types and helpful error messages.


26-52: ⚠️ Potential issue

Consider cleanup of inner liveQuery subscription.

The outer liveQuery's cleanup is handled by the useEffect hook, but the inner liveQuery created in the onData callback might leak if the outer query updates before the inner query completes.

Let's verify if there are similar patterns in the codebase:

Consider updating the code to ensure proper cleanup:

 return liveQuery<TransactionEntity>(getHasTransactionsQuery(schedules), {
   onData: data => {
+    // Clean up previous status query if it exists
+    statusQueryRef.current?.unsubscribe();
     const hasTrans = new Set(data.filter(Boolean).map(row => row.schedule));
     // ... rest of the code
   },
   onError,
 });
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6)

21-28: Well-structured import organization!

The introduction of dedicated transaction hooks (useTransactions, usePreviewTransactions, useTransactionsSearch) improves code organization and reusability.


59-62: Good use of useMemo for query optimization!

The memoization of schedulesQuery prevents unnecessary recalculations when the component re-renders.


217-222: Excellent type safety improvements!

The enhanced type definitions for accountId properly handle all possible account types, including special cases like 'budgeted' and 'offbudget'.


242-243: Clean implementation of preview transactions!

Good separation of concerns between regular and preview transactions, with proper loading state handling.


315-318: Clean implementation of transaction display logic!

The conditional concatenation of preview and regular transactions is well-implemented and handles the search state appropriately.


224-228: 🛠️ Refactor suggestion

Consider adding error handling options to the query

The base query could benefit from additional options to handle edge cases.

 const baseTransactionsQuery = useCallback(
   () =>
-    queries.transactions(accountId).options({ splits: 'none' }).select('*'),
+    queries.transactions(accountId).options({
+      splits: 'none',
+      errorHandling: 'continue',
+    }).select('*'),
   [accountId],
 );

Likely invalid or redundant comment.

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

61-61: Use undefined instead of void in union type

The type () => void | null can be confusing. Using undefined instead of void in the union type would improve clarity.

🧰 Tools
🪛 Biome

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

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)


103-104: Initialize class properties with appropriate types

The properties _data and _dependencies are initialized to null but their types are Data<TResponse> and Set<string> respectively.


286-290: Await fetchCount in run method

The fetchCount() method is asynchronous but called without awaiting its completion. This could lead to race conditions where _totalCount is not updated before it's needed.


422-425: Validate totalCount updates in optimisticUpdate

The optimisticUpdate method modifies _totalCount without validation, which could lead to inconsistent state if the update function removes or adds multiple items.


10-21: 🛠️ Refactor suggestion

Consider adding type constraints to TResponse

The generic type parameter TResponse could benefit from type constraints to ensure it's an object type, making it more type-safe.

-export function liveQuery<TResponse = unknown>(
+export function liveQuery<TResponse extends Record<string, unknown> = Record<string, unknown>>(

Likely invalid or redundant comment.


214-234: 🛠️ Refactor suggestion

Add request cancellation cleanup

The request handling could be improved by cleaning up resources when the query is unsubscribed during an in-flight request.

   const reqId = Math.random();
   this.inflightRequestId = reqId;

   try {
     const { data, dependencies } = await runQuery();
+    
+    // Clean up if unsubscribed during request
+    if (!this._unsubscribeSyncEvent) {
+      this.inflightRequestId = null;
+      return;
+    }

     if (this._dependencies == null) {
       this._dependencies = new Set(dependencies);
     }

Likely invalid or redundant comment.

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

140-140: LGTM: Parameter structure update for doQuery

The change to use an object parameter with onData callback is consistent with modern JavaScript practices and improves the API's flexibility.


156-159: LGTM: Clear separation of callback and options

The nested structure with separate onData and options properties improves clarity and maintainability.


174-177: LGTM: Consistent parameter structure across test cases

The object parameter pattern is consistently applied across all test cases, improving code maintainability and readability.

Also applies to: 200-202, 230-233, 254-257


279-281: LGTM: Optional options demonstrated

The test case correctly demonstrates that the options object is optional when only callbacks are needed.


361-363: LGTM: Consistent pagination configuration

The test cases properly demonstrate different pagination configurations using the new parameter structure.

Also applies to: 378-380, 404-407


440-443: LGTM: Advanced pagination features

The test cases properly verify complex pagination scenarios while maintaining consistency with the new parameter structure.

Also applies to: 484-487

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

301-304: Improved liveQuery implementation for better state management

The refactored callback structure using onData property provides better clarity and aligns with best practices for handling live data updates.


344-347: Consistent implementation of liveQuery pattern

The refactored callback structure maintains consistency with other liveQuery implementations, contributing to more predictable state updates and reduced UI flickering.


Line range hint 301-347: Verify fix for schedules page flickering

The refactored liveQuery implementations should help address the flickering issues mentioned in the PR objectives. Please verify that the flickering is resolved by:

  1. Using developer tools to set a 20x slowdown
  2. Switching away from and back to the Schedules page
  3. Confirming that the loading states are properly handled
✅ Verification successful

Based on the shell script results, I can now generate a final response:

Loading states are properly implemented to prevent flickering

The codebase shows proper loading state handling:

  • useCachedSchedules hook provides isLoading state which is used to prevent premature rendering
  • State management in ScheduleDetails.jsx handles transitions properly through multiple effects
  • Transactions loading is managed through liveQuery subscriptions with proper cleanup

The implementation includes:

  • Loading state checks before rendering content
  • Proper unsubscribe cleanup in useEffect hooks
  • Controlled state updates through dispatch actions
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for loading state handling in schedule-related components
rg -A 5 'isLoading|loading' --type js --type jsx --type tsx

Length of output: 89


Script:

#!/bin/bash
# Search for loading state handling in schedule-related components with correct file extensions
rg -A 5 'isLoading|loading' . --type js

# Also check for state management related to loading in ScheduleDetails
rg -A 5 'state\.' packages/desktop-client/src/components/schedules/ScheduleDetails.jsx

# Look for reducer actions that might handle loading states
rg "type: '[^']*'" packages/desktop-client/src/components/schedules/ScheduleDetails.jsx

Length of output: 20874

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

Line range hint 333-375: Address TODO comment about test flakiness.

There's a TODO comment about fixing flakiness in the keyboard navigation test. This should be addressed to ensure test reliability.

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

770-777: Verify layout stability during schedule loading.

The current implementation might cause layout shifts when schedules are loaded, as elements suddenly appear or disappear.

Let's verify the impact on layout stability:

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

32-97: Well-implemented useTransactions hook

The hook correctly handles transaction fetching, loading, and error states. The use of optionsRef to manage options without causing unnecessary re-renders aligns with best practices and previous discussions.


105-191: Effective implementation of usePreviewTransactions hook

The hook successfully generates preview transactions based on cached schedules, manages asynchronous operations properly, and correctly handles loading and error states.


213-227: 🛠️ Refactor suggestion

Consider replacing lodash/debounce with useDebounce hook for better integration

Using the useDebounce hook from the use-debounce package can enhance integration with React's hook system and simplify cleanup, providing a more idiomatic React solution.

Comment on lines +279 to +283
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.

🛠️ Refactor suggestion

Consider debouncing the search updates

The search implementation could benefit from debouncing to prevent excessive query updates during rapid user input.

Consider using a debounced version of setTransactionsQuery for better performance during search.

Comment on lines +1881 to +1884
const schedulesQuery = useMemo(
() => accountSchedulesQuery(params.id),
[params.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

Handle potential undefined params.id

The accountSchedulesQuery is called with params.id which could be undefined, potentially causing runtime errors.

Apply this diff to handle the undefined case:

  const schedulesQuery = useMemo(
-   () => accountSchedulesQuery(params.id),
+   () => accountSchedulesQuery(params.id || ''),
    [params.id],
  );

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +67 to +74
select(
exprs:
| Array<ObjectExpression | string>
| ObjectExpression
| string
| '*'
| ['*'] = [],
) {
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

Clarify the intended behavior of the 'select' method compared to other methods

The select method replaces the existing selectExpressions, whereas methods like filter, groupBy, and orderBy append to their respective expressions. If this difference is intentional, consider documenting it to ensure developers are aware of this behavior.

Comment on lines +132 to 134
options(opts: Record<string, unknown>) {
return new Query({ ...this.state, tableOptions: opts });
}
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 merging 'tableOptions' instead of replacing them in the 'options' method

The options method currently replaces the entire tableOptions object. If options is called multiple times, previous options will be overwritten. To allow cumulative option setting, consider merging the new opts with the existing tableOptions.

Apply this diff to merge the options:

options(opts: Record<string, unknown>) {
-  return new Query({ ...this.state, tableOptions: opts });
+  return new Query({
+    ...this.state,
+    tableOptions: { ...this.state.tableOptions, ...opts },
+  });
}
📝 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
options(opts: Record<string, unknown>) {
return new Query({ ...this.state, tableOptions: opts });
}
options(opts: Record<string, unknown>) {
return new Query({
...this.state,
tableOptions: { ...this.state.tableOptions, ...opts },
});
}

Comment on lines +84 to 87
calculate(expr: ObjectExpression | string) {
const query = this.select({ result: expr });
query.state.calculation = true;
return query;
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

Avoid mutating state directly in the 'calculate' method

In the calculate method, query.state.calculation = true; directly mutates the state of the Query instance. To maintain immutability and consistency with the other methods, consider creating a new Query instance with calculation: true in the state.

Apply this diff to avoid direct state mutation:

calculate(expr: ObjectExpression | string) {
-  const query = this.select({ result: expr });
-  query.state.calculation = true;
-  return query;
+  return new Query({
+    ...this.select({ result: expr }).state,
+    calculation: true,
+  });
}

Committable suggestion skipped: line range outside the PR's diff.

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.

3 participants