-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate Account component to a functional component and use the new useTransactions
hook
#3708
base: master
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for actualbudget ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
2f168e1
to
fddd46f
Compare
Bundle Stats — desktop-clientHey 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
Changeset
View detailed bundle breakdownAdded No assets were added Removed No assets were removed Bigger No assets were bigger Smaller
Unchanged
|
Bundle Stats — loot-coreHey 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
Changeset No files were changed View detailed bundle breakdownAdded No assets were added Removed No assets were removed Bigger No assets were bigger Smaller No assets were smaller Unchanged
|
useTransactions
hook
Warning Rate limit exceeded@joel-jeremy has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 48 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. WalkthroughThe pull request introduces a series of modifications across multiple components in the desktop client, primarily focusing on state management, parameter naming, and loading state handling. Key changes include the replacement of Possibly related PRs
Suggested labels
Suggested reviewers
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 17
🧹 Outside diff range and nitpick comments (41)
packages/desktop-client/src/hooks/useNotes.ts (1)
Line range hint
8-12
: LGTM: Function updated to useuseQuery
correctly.The changes successfully migrate the
useNotes
function to use the newuseQuery
hook. The query logic remains intact, and the use ofuseMemo
for deriving the note content is a good practice for performance optimization.Consider adding a comment explaining the
useMemo
logic for better clarity:return useMemo( // Return the first note's content if available, otherwise null () => (data && data.length > 0 ? data[0].note : null), [data] );packages/loot-core/src/client/data-hooks/widget.ts (1)
8-10
: Approved with a minor suggestion.The implementation change to
useQuery
is consistent with the import modification and PR objectives. The inclusion of bothid
andtype
in the dependency array enhances the hook's reactivity, which is a good improvement.Consider memoizing the query function to optimize performance:
const queryFn = useCallback(() => q('dashboard').filter({ id, type }).select('*'), [id, type]); const { data = [], isLoading } = useQuery<W>(queryFn, [id, type]);This change can prevent unnecessary re-creation of the query function on each render.
packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1)
11-13
: LGTM: Excellent addition for improved synchronization.The new
waitFor
method is a great addition that ensures the account list is fully loaded before any interactions occur. This aligns well with the PR objective and improves test reliability.Consider adding a timeout and error handling to make the wait more robust:
async waitFor() { - await this.accountList.waitFor(); + try { + await this.accountList.waitFor({ timeout: 5000 }); + } catch (error) { + throw new Error('Account list did not load within the expected time'); + } }packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)
19-19
: LGTM: Hook usage updated correctly with a minor suggestion.The destructuring of the
useSchedules
hook has been appropriately updated to align with its new structure. The default value forschedules
is a good practice to prevent potential issues.Consider using TypeScript's non-null assertion operator if you're certain that
schedules
will never be undefined:const { schedules!, isLoading } = useSchedules();This would eliminate the need for the default value while still ensuring type safety. However, only use this if you're absolutely sure that
schedules
will always be defined when not loading.
21-23
: LGTM: Loading state handled correctly with a suggestion for enhancement.The addition of the
isLoading
check prevents rendering the component when data is still loading, which is a good practice for improving user experience.Consider enhancing the user experience further by showing a loading indicator instead of returning
null
. This would provide visual feedback to the user that data is being fetched. Here's an example:if (isLoading) { return <LoadingSpinner />; // Assuming you have a LoadingSpinner component }This change would make the loading state more apparent to the user, improving the overall user experience.
packages/desktop-client/e2e/page-models/mobile-account-page.js (1)
36-38
: LGTM! Consider adding error handling.The
clearSearch()
method is a useful addition that provides a clean way to reset the search state. This is particularly valuable for test setup and teardown processes.Consider adding a try-catch block to handle potential errors when clearing the search box. This could improve the robustness of the method:
async clearSearch() { try { await this.searchBox.clear(); } catch (error) { console.error('Failed to clear search box:', error); throw error; } }packages/loot-core/src/client/data-hooks/reports.ts (1)
57-58
: Good updates to the return value!The inclusion of
isLoading
in the return object and the null/undefined check forqueryData
are excellent improvements. They provide more information to the hook consumers and make the code more robust.A minor optimization suggestion:
Consider using the nullish coalescing operator for a more concise expression:
-data: sort(toJS(queryData ? [...queryData] : [])), +data: sort(toJS([...queryData ?? []])),This achieves the same result but is slightly more readable.
packages/desktop-client/src/hooks/usePreviewTransactions.ts (1)
26-31
: LGTM: Improved data handling and performanceThe use of
useCachedSchedules
andusePrevious
hooks, along with the early return when data is loading, significantly improves the hook's performance and data handling. This aligns well with React best practices.Consider adding a loading state to the return value for better UX handling:
if (scheduleData.isLoading) { return { transactions: [], isLoading: true }; } // ... rest of the hook logic return { transactions: previewTransactions, isLoading: false };This change would allow consumers of the hook to easily display loading indicators when necessary.
packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (2)
36-41
: Improved hook usage and query efficiency.The refactoring of the
useSchedules
hook usage is well done. DestructuringisLoading
andschedules
directly improves readability. The introduction ofqueryBuilder
withuseCallback
is a good practice for memoization.Consider renaming
isLoading
toisSchedulesLoading
for consistency with its usage on line 43:- const { isLoading: isSchedulesLoading, schedules } = useSchedules({ + const { isLoading: isSchedulesLoading, schedules } = useSchedules({
54-54
: Improved null safety in render logic.The use of optional chaining when accessing
schedule
properties is a good improvement for handling potential undefined values.Consider adding a null check before formatting the date to avoid unnecessary function calls:
- {format(schedule?.next_date || '', 'MMMM dd, yyyy')} + {schedule?.next_date ? format(schedule.next_date, 'MMMM dd, yyyy') : ''}This ensures that
format
is only called whennext_date
is available.Also applies to: 68-68
packages/desktop-client/src/components/schedules/index.tsx (1)
40-68
:onAction
function memoized and action handling consolidated.The
onAction
function is now memoized usinguseCallback
, which is consistent with the other functions in this component. The empty dependencies array is correct as the function doesn't rely on any external variables.Consider extracting the logic for each action into separate functions for improved maintainability. This would make the code more modular and easier to test. For example:
const handlePostTransaction = useCallback(async (id: ScheduleEntity['id']) => { await send('schedule/post-transaction', { id }); }, []); const handleSkip = useCallback(async (id: ScheduleEntity['id']) => { await send('schedule/skip-next-date', { id }); }, []); // ... other action handlers ... const onAction = useCallback(async (name: ScheduleItemAction, id: ScheduleEntity['id']) => { switch (name) { case 'post-transaction': return handlePostTransaction(id); case 'skip': return handleSkip(id); // ... other cases ... default: throw new Error(`Unknown action: ${name}`); } }, [handlePostTransaction, handleSkip /*, other handlers */]);This approach would make each action more isolated and easier to maintain or modify in the future.
packages/desktop-client/src/components/accounts/Reconcile.tsx (2)
28-28
: Improved ReconcilingMessage component with dynamic sheet value namingThe changes in the
ReconcilingMessage
component effectively utilize the newaccountId
prop and simplify theuseSheetValue
hook usage:
- Dynamic sheet value naming using
accountId
increases flexibility.- Simplified query filtering may improve performance.
These modifications align well with the PR objectives and the type changes.
For consistency, consider using a template literal for the
name
property:name: `balance-query-${accountId}-cleared`,This matches the type definition more closely and improves readability.
Also applies to: 35-38
Line range hint
1-24
: Summary: Successful migration to new structure with improved flexibilityThe changes in this file effectively:
- Enhance type definitions for better type safety.
- Improve component flexibility with the addition of
accountId
.- Simplify query handling in the
useSheetValue
hook.These modifications align well with the PR objectives of migrating to functional components and utilizing the new
useTransactions
hook. The code maintains good readability and follows React best practices.Consider documenting the purpose of the
accountId
prop and its relationship to thebalanceQuery
in a comment above theReconcilingMessageProps
type. This would help future developers understand the component's design more quickly.Also applies to: 25-38
packages/desktop-client/e2e/accounts.test.js (1)
114-114
: Consistent use of explicit wait improves test reliability.The addition of
await accountPage.waitFor();
after account creation is a good practice, ensuring the page is fully loaded before proceeding with import tests. This change improves test reliability and is consistent with the earlier change.For better maintainability, consider extracting this common wait pattern into a helper method in the
AccountPage
class, such ascreateAndWaitForAccount()
. This would encapsulate the account creation and waiting logic, making it easier to reuse and modify in the future.packages/desktop-client/e2e/page-models/account-page.js (1)
33-35
: Approve the addition of thewaitFor()
method with a suggestion for error handling.The new
waitFor()
method is a valuable addition to theAccountPage
class. It enhances the reliability of e2e tests by ensuring the transaction table is ready before proceeding with interactions.Consider adding a timeout and error handling to make the method more robust:
async waitFor(timeout = 30000) { try { await this.transactionTable.waitFor({ timeout }); } catch (error) { throw new Error(`Transaction table not ready after ${timeout}ms: ${error.message}`); } }This change would allow customizable timeout and provide more informative error messages.
packages/desktop-client/src/components/transactions/TransactionList.jsx (1)
Line range hint
93-143
: Optimization of transaction update functions looks good.The changes to
onAdd
,onSave
,onAddSplit
, andonSplit
functions appear to optimize the update process by reducing unnecessary refetches. This aligns well with the PR objectives of improving transaction handling efficiency.The new behavior in
onSave
for updatingsort_order
when a transaction's date changes is a good addition to maintain proper sorting.Consider extracting the date change logic in
onSave
into a separate function for better readability:const handleDateChange = (updatedTransaction) => { updatedTransaction.sort_order = Date.now(); return saveDiff({ updated: [updatedTransaction] }).then(onRefetch); }; // In onSave: if (dateChanged) { await handleDateChange(changes.diff.updated[0]); } else { // ... existing else block }packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (3)
Line range hint
40-86
: Enhance AccountCard accessibilityConsider adding more accessibility attributes to the AccountCard component to improve the user experience for screen reader users:
- Add an
aria-label
to the Button component that includes the account name and balance.- Use
aria-live="polite"
on the status indicator (the colored dot) to announce status changes.Example implementation:
<Button onPress={() => onSelect(account.id)} aria-label={`${account.name} account, balance ${props.formattedValue}`} // ... other props > {/* ... existing content */} <View aria-live="polite" aria-label={`Account status: ${pending ? 'pending' : failed ? 'failed' : 'connected'}`} style={{ // ... existing styles }} /> {/* ... rest of the component */} </Button>These changes will provide more context to screen reader users about the account details and status.
Line range hint
89-99
: Improve EmptyMessage componentThe current EmptyMessage component provides basic information, but it could be more helpful to users. Consider adding:
- A clear call-to-action button to add an account.
- More specific instructions on how to add an account.
Example implementation:
function EmptyMessage({ onAddAccount }) { return ( <View style={{ flex: 1, padding: 30 }}> <Text style={styles.text}> To get started with Actual, you need to add an account. You can: • Link an account to automatically download transactions • Manage an account locally yourself </Text> <Button onPress={onAddAccount} style={{ marginTop: 20 }}> Add Your First Account </Button> </View> ); }This change would provide clearer guidance and a direct action for new users.
Line range hint
258-260
: Add user feedback for sync actionThe
onSync
function currently dispatches an action but doesn't provide any feedback to the user. Consider adding a visual indicator or notification to inform the user that a sync is in progress.Example implementation:
const onSync = () => { dispatch(syncAndDownload()); // Add a toast notification or update a loading state showToast('Syncing accounts...'); // Or update a local state // setIsSyncing(true); };This change would improve the user experience by providing clear feedback on the sync action.
packages/loot-core/src/client/queries.ts (2)
31-34
: LGTM! Consider using a type alias for improved readability.The changes to the
accountFilter
function (previouslygetAccountFilter
) look good. The function now handles a wider range of account types, improving its flexibility while maintaining backwards compatibility.Consider creating a type alias for the
accountId
parameter to improve code readability:type AccountIdType = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized'; export function accountFilter( accountId?: AccountIdType, field = 'account', ) { // ... rest of the function } export function transactions( accountId?: AccountIdType, ) { // ... rest of the function }This change would make the function signatures more concise and easier to understand at a glance.
Also applies to: 70-72
83-85
: LGTM! Consider updating the function signature for consistency.The renaming of
makeTransactionSearchQuery
totransactionsSearch
is a good improvement, making the function name more concise and clear.For consistency with the other updated functions, consider updating the
accountId
parameter in thecurrentQuery
to use the same type asaccountFilter
andtransactions
:export function transactionsSearch( currentQuery: Query & { accountId?: AccountIdType }, search: string, dateFormat: SyncedPrefs['dateFormat'], ) { // ... rest of the function }This change would ensure that all transaction-related functions use consistent types for account IDs.
packages/desktop-client/src/hooks/useSelected.tsx (2)
Line range hint
1-1
: Consider removing the// @ts-strict-ignore
commentThe
// @ts-strict-ignore
comment at the top of the file disables strict TypeScript checking. It's generally a good practice to enable strict checking to catch potential type-related issues early. Consider removing this comment and addressing any TypeScript errors that may arise.Would you like assistance in removing the
// @ts-strict-ignore
comment and addressing any resulting TypeScript errors?
Line range hint
1-353
: Consider performance optimizations for large listsThe current implementation of
useSelected
and related components is well-structured and functional. However, for large lists of items, there might be room for performance optimization. Consider the following suggestions:
- Memoize the
items
array passed touseSelected
to prevent unnecessary re-renders.- Use
useMemo
for expensive computations within the hook, such as filtering items in theselect-all
action.- Consider using a virtual list component for rendering large lists of selectable items to improve rendering performance.
Would you like me to provide code examples for these optimizations?
packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (1)
42-42
: LGTM! Consider adding JSDoc comment for clarity.The addition of the optional
isLoading
prop is a good improvement, allowing the component to handle loading states. This aligns well with the PR's objective of enhancing component functionality.Consider adding a JSDoc comment to explain the purpose of this prop:
/** Indicates whether the component is in a loading state */ isLoading?: boolean;packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)
253-253
: Good use of readonly type for derived state!The change from
SchedulesTableItem[]
toreadonly SchedulesTableItem[]
for theitems
variable is consistent with the earlier modification to theschedules
prop type. It enforces immutability on the derived state, improving type safety and preventing accidental modifications within the component.For consistency, consider updating the return type of the
useMemo
hook to explicitly declare it asreadonly SchedulesTableItem[]
.You can make this change by updating the line as follows:
const items = useMemo<readonly SchedulesTableItem[]>(() => {packages/loot-core/src/client/query-helpers.test.ts (1)
313-313
: LGTM: Updated property access and assertions for pagedQueryThe changes correctly update the property access on the
paged
object to align with the new structure returned bypagedQuery
. The additional assertion forhasNext
enhances the test coverage.Consider adding a comment explaining the significance of the
hasNext
property to improve test readability.Also applies to: 324-324, 335-335, 347-348
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (4)
243-243
: Optimized category parsingThe
parseCategoryFields
function has been updated to work with the new category structure. This change aligns well with theuseCategories
hook implementation.Consider further optimizing this function by using
find
instead offorEach
:function parseCategoryFields(trans, categories) { return categories.find(category => category.id === trans.category || category.name === trans.category )?.id || null; }This approach is more concise and stops iterating as soon as a match is found, potentially improving performance for large category lists.
Also applies to: 587-587
158-159
: Streamlined category handling inImportTransactionsModal
The changes in how categories are obtained and passed to child components are consistent with the new hook-based approach. This simplifies the component's structure and improves maintainability.
Consider adding error handling for the
useCategories
hook:const { list: categories, error: categoriesError } = useCategories(); // Later in the component if (categoriesError) { // Handle the error, e.g., display an error message }This addition would make the component more robust by gracefully handling potential errors in category fetching.
Also applies to: 789-789
243-243
: Consistent category handling ingetImportPreview
andonImport
The modifications in
getImportPreview
andonImport
functions align well with the new category management approach. The simplified category parsing maintains functionality while adapting to the new data structure.Consider creating a category lookup map for potential performance improvement:
const categoryMap = new Map(categories.map(cat => [cat.name, cat.id])); // Then in parseCategoryFields function parseCategoryFields(trans, categoryMap) { return categoryMap.get(trans.category) || trans.category; }This approach could significantly speed up category lookups, especially for large category lists or when processing many transactions.
Also applies to: 587-587
Line range hint
1-1086
: Overall improvement in category management and component structureThe changes in this file represent a significant improvement in how categories are managed within the
ImportTransactionsModal
component. Key improvements include:
- Centralized category management using the
useCategories
hook.- Simplified props and internal logic for handling categories.
- Consistent updates throughout the file, maintaining overall functionality.
These changes enhance the component's maintainability and potentially its performance. The refactoring aligns well with React best practices and sets a good foundation for future improvements.
As the component grows in complexity, consider breaking it down into smaller, more focused components. This could further improve maintainability and make the code easier to test.
packages/loot-core/src/client/data-hooks/filters.ts (2)
Line range hint
8-16
: Add type annotation to the 'rows' parameter in 'toJS' functionThe
toJS
function currently lacks a type annotation for therows
parameter. Adding an explicit type enhances type safety and code readability.Apply this diff to add the parameter type:
-function toJS(rows): TransactionFilterEntity[] { +function toJS(rows: any[]): TransactionFilterEntity[] {Replace
any[]
with the appropriate type if known.
29-35
: Handle potential undefined 'name' properties during sortingWhen sorting the filters, if any
name
property isundefined
ornull
, it could cause a runtime error. Consider adding a check to ensurename
is defined before callingtrim()
.Apply this diff to handle undefined
name
properties:return filters.slice().sort((a, b) => - a.name + (a.name || '') .trim() .localeCompare((b.name || '').trim(), undefined, { ignorePunctuation: true }), );packages/loot-core/src/client/query-hooks.ts (2)
28-29
: Consider providing a more descriptive error message when query is nullSetting the error to
new Error('Query is null')
might not provide sufficient context. Consider enhancing the error message to explain why the query is null or provide guidance on how to resolve it.
43-44
: Enhance error handling in theonError
callbackCurrently,
onError: setError
sets the error state with the error received fromliveQuery
. Consider adding logging or additional error processing to provide more context when debugging issues arising from the query.packages/loot-core/src/shared/query.ts (1)
136-138
: Consider preservingtableOptions
in thereset
methodCurrently, the
reset
method creates a newQuery
with only thetable
property, which might unintentionally discard existingtableOptions
. IftableOptions
should persist after a reset, consider modifying the method to include them.You might update the
reset
method as follows:reset() { - return q(this.state.table); + return new Query({ + table: this.state.table, + tableOptions: this.state.tableOptions, + }); }packages/desktop-client/src/components/accounts/Balance.jsx (2)
20-21
: Add an empty line between import groups for better readability.According to the project's style guidelines, there should be at least one empty line between different groups of imports. This enhances code readability and organization.
Apply this diff to add an empty line between import groups:
import { useSheetValue } from '../spreadsheet/useSheetValue'; +import { runQuery } from 'loot-core/client/query-helpers';
Note: After removing the unused
runQuery
import as suggested earlier, this change might not be necessary.🧰 Tools
🪛 GitHub Check: lint
[warning] 20-20:
There should be at least one empty line between import groups
[warning] 21-21:
loot-core/client/query-helpers
import should occur before import ofloot-core/shared/transactions
[warning] 21-21:
'runQuery' is defined but never used. Allowed unused vars must match /^(_|React)/u
185-195
: Consider adding parentheses for clarity in complex expressions.In the CSS styling for the
SvgArrowButtonRight1
component, adding parentheses around the ternary expressions can improve readability and prevent potential issues with operator precedence.Apply this diff to add parentheses:
opacity: - selectedItems.size > 0 || showExtraBalances ? 1 : 0, + (selectedItems.size > 0 || showExtraBalances) ? 1 : 0,packages/loot-core/src/client/query-helpers.ts (1)
Line range hint
383-417
: Potential for infinite recursion in_fetchNext
method.In the
_fetchNext
method, ifthis.inflightRequestId
orpreviousData !== this.data
is true, the method recursively calls itself without any delay. In situations where data keeps changing, this could potentially lead to a stack overflow or performance issues.Consider adding a maximum retry limit or introducing a delay between retries to prevent potential infinite recursion.
packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (1)
Line range hint
166-166
: Fix typo in commentThere's a typo in the comment: "defalut" should be "default".
Apply this diff to correct the typo:
- // defalut to a non-zero value so the sign can be changed before the value + // default to a non-zero value so the sign can be changed before the valuepackages/desktop-client/src/components/modals/EditRuleModal.jsx (1)
324-324
: Improve user feedback when no schedules are foundInstead of displaying the
id
, consider showing a user-friendly message to enhance the user experience.Apply this diff to update the message:
-if (schedules.length === 0) { - return <View style={{ flex: 1 }}>{id}</View>; +if (schedules.length === 0) { + return <View style={{ flex: 1 }}>No schedules available.</View>; }packages/desktop-client/src/components/accounts/Header.tsx (1)
381-392
: Enhance accessibility for the Reconcile buttonThe
Reconcile
button uses aView
component with atitle
prop for the tooltip, which may not be accessible to all users. Consider adding anaria-label
directly to the button or using a tooltip component that ensures better accessibility support.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
upcoming-release-notes/3685.md
is excluded by!**/*.md
📒 Files selected for processing (43)
- .eslintrc.js (1 hunks)
- packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
- packages/desktop-client/e2e/accounts.test.js (2 hunks)
- packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
- packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
- packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
- packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
- packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
- packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
- packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
- packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
- packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
- packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
- packages/desktop-client/src/components/modals/EditRuleModal.jsx (1 hunks)
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (2 hunks)
- packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx (1 hunks)
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2 hunks)
- packages/desktop-client/src/components/schedules/index.tsx (2 hunks)
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
- packages/desktop-client/src/components/table.tsx (4 hunks)
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
- packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
- packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
- packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
- packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
- packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
- packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
- packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
- packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
- packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
- packages/loot-core/src/client/queries.ts (2 hunks)
- packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
- packages/loot-core/src/client/query-helpers.ts (4 hunks)
- packages/loot-core/src/client/query-hooks.ts (1 hunks)
- packages/loot-core/src/shared/query.ts (9 hunks)
- packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useSchedules.ts
🧰 Additional context used
📓 Learnings (5)
.eslintrc.js (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: .eslintrc.js:164-169 Timestamp: 2024-10-18T15:33:24.453Z Learning: When updating the `additionalHooks` pattern for `react-hooks/exhaustive-deps`, only `useQuery` should be added, not generalized patterns.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (4)
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-10-08T15:46:15.739Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-09-27T14:15:46.637Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: MatissJanis PR: actualbudget/actual#3570 File: packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx:83-90 Timestamp: 2024-10-08T15:46:15.739Z Learning: In the `Transaction` component in `Transaction.tsx`, both `rawTransaction` and `transaction` should be included in the dependency arrays of `useMemo` hooks, even though `transaction` derives from `rawTransaction`.
Learnt from: MatissJanis PR: actualbudget/actual#3570 File: packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx:83-90 Timestamp: 2024-10-05T10:58:13.598Z Learning: In the `Transaction` component in `Transaction.tsx`, both `rawTransaction` and `transaction` should be included in the dependency arrays of `useMemo` hooks, even though `transaction` derives from `rawTransaction`.
packages/loot-core/src/client/data-hooks/dashboard.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/reports.ts:8-8 Timestamp: 2024-10-18T15:37:01.917Z Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/data-hooks/reports.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/reports.ts:8-8 Timestamp: 2024-10-18T15:37:01.917Z Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/reports.ts:8-8 Timestamp: 2024-10-18T15:37:01.917Z Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 GitHub Check: lint
packages/desktop-client/src/components/accounts/Balance.jsx
[warning] 1-1:
'useEffect' is defined but never used. Allowed unused vars must match /^(_|React)/u
[warning] 1-1:
'useState' is defined but never used. Allowed unused vars must match /^(_|React)/u
[warning] 20-20:
There should be at least one empty line between import groups
[warning] 21-21:
loot-core/client/query-helpers
import should occur before import ofloot-core/shared/transactions
[warning] 21-21:
'runQuery' is defined but never used. Allowed unused vars must match /^(_|React)/upackages/desktop-client/src/hooks/usePreviewTransactions.ts
[warning] 19-19:
exported declaration 'usePreviewTransactions' not used within other modules
🪛 Biome
packages/loot-core/src/client/query-helpers.ts
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
🔇 Additional comments (108)
packages/desktop-client/src/hooks/useNotes.ts (1)
3-3
: LGTM: Import statement updated correctly.The import statement has been updated to use the new
useQuery
hook from the centralized location. This change aligns with the PR objective and improves code organization.packages/loot-core/src/client/data-hooks/dashboard.ts (4)
5-5
: LGTM: Import statement foruseQuery
added.The import statement for
useQuery
is correctly added and aligns with the new implementation.
8-10
: LGTM: Successful migration fromuseLiveQuery
touseQuery
.The implementation has been correctly updated to use
useQuery
instead ofuseLiveQuery
. The destructuring now includes bothdata
andisLoading
, which aligns with theuseQuery
hook's return value. The type annotation and query function remain consistent, ensuring type safety and maintaining the existing query logic.
15-18
: LGTM: Return statement and useMemo dependency array updated correctly.The changes in the return object and
useMemo
dependency array are appropriate:
isLoading
is now directly used from theuseQuery
hook, which is more efficient.- The fallback to an empty array for
data
is maintained, ensuring consistent behavior.- The
useMemo
dependency array now correctly includes bothisLoading
andqueryData
, ensuring proper recalculation of the memoized value.These changes align well with the new
useQuery
implementation and maintain the component's reactivity.
Line range hint
1-20
: Overall: Excellent migration touseQuery
with improved state handling.The changes in this file successfully achieve the PR's objective of migrating to functional components and utilizing the new
useQuery
hook. Key improvements include:
- Proper implementation of
useQuery
, replacinguseLiveQuery
.- Enhanced state management with direct access to
isLoading
.- Improved reactivity in the
useMemo
hook with an updated dependency array.These changes align well with the codebase's evolution towards more efficient data fetching and state management. The implementation is consistent with the known behavior of
useLiveQuery
as a wrapper arounduseQuery
, now directly usinguseQuery
for better control over the returned data and loading state.packages/loot-core/src/client/data-hooks/widget.ts (3)
5-5
: LGTM. Verify new hook functionality.The change from
useLiveQuery
touseQuery
aligns with the PR objectives. This shift in querying mechanism is likely part of a larger refactoring effort.To ensure this change doesn't introduce any regressions, please verify that
useQuery
provides similar functionality touseLiveQuery
in the context of this component.
7-7
: Great improvement in type safety!The updated function signature enhances type safety by ensuring that the
id
parameter matches theid
type of theWidget
generic type. This change will help catch potential type-related errors at compile-time, improving overall code reliability.
18-18
: Excellent update to useMemo dependencies!Including
isLoading
in theuseMemo
dependency array is a great improvement. This change ensures that the memoized object updates correctly when the loading state changes, not just when the data changes. It aligns with React's best practices for hook dependencies and improves the overall reliability of theuseWidget
hook.packages/desktop-client/e2e/page-models/mobile-accounts-page.js (2)
7-7
: LGTM: Good addition for improved element selection.The new
accountList
property usinggetByLabel
is a positive change. It enhances accessibility and maintainability by selecting elements based on their labels rather than implementation-specific attributes.
Line range hint
1-38
: Overall, excellent improvements to the MobileAccountsPage model.The changes in this file significantly enhance the robustness of the e2e tests for the mobile accounts page. The new
accountList
property andwaitFor
method work together to ensure proper synchronization and reliable element selection. These improvements align perfectly with the PR's objective of enhancing code structure and efficiency.packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)
3-3
: LGTM: Import statement updated correctly.The import for
useSchedules
has been appropriately updated to use the centralized hook from the core library. This change improves code organization and promotes reusability across the application.
Line range hint
1-38
: Overall, the changes look good and align with the PR objectives.The modifications to
ScheduleValue.tsx
successfully migrate the component to use the newuseSchedules
hook from the core library. The changes improve error handling and align with best practices for React functional components. The suggestions provided for using the non-null assertion operator and adding a loading indicator are optional enhancements that could further improve the component.packages/desktop-client/e2e/page-models/mobile-account-page.js (2)
18-20
: LGTM! Great addition for test reliability.The
waitFor()
method is a valuable addition that enhances the robustness of the tests. By ensuring thetransactionList
element is present before proceeding, it helps prevent race conditions and flaky tests.This aligns well with similar changes in other files, promoting consistent synchronization across the application's test suite.
Line range hint
1-53
: Overall, these changes enhance test reliability and align with PR objectives.The additions to the
MobileAccountPage
class improve synchronization with UI state and provide useful functionality for managing the search feature. These changes contribute to the overall goal of improving transaction handling and test reliability across the application.packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (3)
36-37
: LGTM: Simplified ref updateMoving the
latestOnChange
ref update outside ofuseLayoutEffect
is a good change. It ensures that the latestonChange
callback is always available and simplifies the code.
38-39
: Good addition: Efficient value trackingThe introduction of the
latestValue
ref is a good optimization. It allows for efficient comparison in theuseLayoutEffect
callback, potentially reducing unnecessary re-renders. The immediate update oflatestValue.current
ensures consistency with the current state.
55-55
: Verify necessity of added dependenciesThe expansion of the
useLayoutEffect
dependency array is good for ensuring the effect re-runs when relevant values change. However, please verify if all these new dependencies (spreadsheet
,bindingObj.name
,bindingObj.query
) are necessary. Consider the following:
- Are all these values expected to change frequently?
- Could any of these cause unnecessary re-renders?
- Is
spreadsheet
a stable reference, or does it change on every render?If any of these dependencies are stable or don't affect the effect's behavior, consider removing them to optimize performance.
To help verify the necessity of these dependencies, you can run the following script:
packages/loot-core/src/client/data-hooks/reports.ts (3)
39-42
: Great implementation of useQuery!The change from
useLiveQuery
touseQuery
is well-implemented. The destructuring to get bothdata
andisLoading
is a good practice, allowing for more granular control over the component's rendering based on the loading state.
60-60
: Correct update to useMemo dependencies.The inclusion of both
isLoading
andqueryData
in the dependency array is correct and necessary. This ensures that the memoized value is recalculated when either the loading state or the query data changes, maintaining the accuracy of the hook's output.
8-8
: LGTM: Import statement updated correctly.The change from
useLiveQuery
touseQuery
aligns with the PR objective of using the new hook structure.Let's verify if this change is applied consistently throughout the file:
packages/desktop-client/e2e/accounts.mobile.test.js (4)
30-30
: Improved test reliability with page load waitThe addition of
await accountsPage.waitFor();
enhances the test's reliability by ensuring the accounts page is fully loaded before proceeding with assertions. This aligns with best practices in end-to-end testing and helps prevent flaky tests caused by timing issues.
41-42
: Consistent improvement in test reliability and readabilityThe addition of
await accountsPage.waitFor();
is consistent with the previous change, further enhancing test reliability. The empty line improves code readability by clearly separating the navigation step from the subsequent actions.
44-44
: Critical wait added before account page interactionsThe addition of
await accountPage.waitFor();
is crucial here as it ensures the individual account page is fully loaded before the subsequent assertions and interactions. This significantly reduces the risk of flaky tests, especially given the multiple assertions that follow this wait statement.
57-59
: Enhanced test coverage with search clear verificationExcellent addition of a new test scenario. The new lines verify that clearing the search functionality works correctly and that transactions are displayed after clearing. This improves the overall test coverage and ensures that the search feature behaves as expected in both directions (filtering and clearing).
The empty line also enhances readability by clearly separating this new scenario from the subsequent search test.
packages/desktop-client/src/hooks/usePreviewTransactions.ts (2)
9-12
: LGTM: Improved type importsThe addition of the
TransactionEntity
type import and the reorganization of imports enhance type safety and code readability.
14-14
: LGTM: Added usePrevious hookThe addition of the
usePrevious
hook import suggests an improvement in state management. This is a good practice for comparing current and previous states.Please ensure that the
usePrevious
hook is correctly implemented and used within the component. You can verify its usage with the following script:✅ Verification successful
The
usePrevious
hook is correctly implemented and properly utilized within theusePreviewTransactions
component.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the implementation and usage of usePrevious hook # Test: Check for the implementation of usePrevious hook echo "Checking for usePrevious hook implementation:" rg -p "export function usePrevious" packages/desktop-client/src/hooks/ # Test: Verify usage of usePrevious in usePreviewTransactions echo "Verifying usage of usePrevious in usePreviewTransactions:" rg -p "usePrevious" packages/desktop-client/src/hooks/usePreviewTransactions.tsLength of output: 606
packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (2)
43-46
: Appropriate loading state handling.The addition of the loading state check is a good practice. Returning
null
while the schedules are loading prevents rendering incomplete data and potential errors.
47-48
: Improved schedule access, but consider empty array case.The use of optional chaining when accessing the first schedule is good practice. However, consider handling the case where
schedules
might be an empty array.Consider adding a check for an empty array:
- const schedule = schedules?.[0]; + const schedule = schedules && schedules.length > 0 ? schedules[0] : undefined; + + if (!schedule) { + // Handle case where no schedule is found + return null; // or some appropriate UI + }This ensures that you're explicitly handling cases where no schedules are returned.
packages/desktop-client/src/components/schedules/index.tsx (4)
1-7
: Imports updated to support functional component and hooks usage.The changes in imports reflect the migration to a functional component and the adoption of hooks. The introduction of
useDispatch
and removal ofuseActions
indicates a shift towards more direct Redux state management, which is a good practice for better control and transparency in state updates.
22-24
: Redux dispatch and local state management implemented correctly.The use of
useDispatch
aligns with modern Redux patterns, providing direct access to the dispatch function. The localfilter
state is managed usinguseState
, which is appropriate for this use case.
25-38
: Functions optimized withuseCallback
and consistent modal dispatch.The
onEdit
,onAdd
, andonDiscover
functions are now memoized usinguseCallback
, which is a good optimization for preventing unnecessary re-renders. The consistent use ofdispatch(pushModal(...))
across these functions improves code readability and maintainability. The dependency arrays are correctly specified, ensuring that the functions are only recreated ifdispatch
changes.
70-73
: Improved data fetching and loading state handling.The use of the
useSchedules
hook simplifies data fetching and provides a loading state. The addition of the loading check before rendering the component content is a good practice. It prevents potential errors from accessing undefined data and improves the user experience by not showing incomplete content.packages/desktop-client/src/components/schedules/ScheduleLink.tsx (1)
42-42
: Approve renaming oftransform
toqueryBuilder
The change from
transform
toqueryBuilder
in theuseSchedules
hook improves code clarity by more accurately describing the function's purpose. This aligns with the broader refactoring efforts mentioned in the PR objectives.To ensure consistency across the codebase, please run the following script:
This script will help identify any inconsistencies in the usage of
useSchedules
across the codebase.✅ Verification successful
Verified renaming of
transform
toqueryBuilder
All instances of
useSchedules
have been successfully updated to usequeryBuilder
. The refactoring is consistent across the codebase.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for any remaining instances of 'transform' in useSchedules calls # and verify that all instances have been updated to 'queryBuilder'. # Search for useSchedules calls echo "Searching for useSchedules calls:" rg "useSchedules\s*\(\s*\{" -g "*.{js,jsx,ts,tsx}" # Search for any remaining 'transform' properties in useSchedules calls echo "Checking for any remaining 'transform' properties in useSchedules calls:" rg "useSchedules\s*\(\s*\{[^}]*transform\s*:" -g "*.{js,jsx,ts,tsx}" # Search for 'queryBuilder' properties in useSchedules calls echo "Verifying 'queryBuilder' usage in useSchedules calls:" rg "useSchedules\s*\(\s*\{[^}]*queryBuilder\s*:" -g "*.{js,jsx,ts,tsx}"Length of output: 1085
Script:
#!/bin/bash # Description: Verify that all useSchedules calls use 'queryBuilder' instead of 'transform'. # This script checks for both explicit ('queryBuilder:') and shorthand ('queryBuilder') property usages. # Search for useSchedules calls echo "Searching for useSchedules calls:" rg "useSchedules\s*\(\s*\{" -g "*.{js,jsx,ts,tsx}" # Ensure no 'transform' properties remain in useSchedules calls echo "Checking for any remaining 'transform' properties in useSchedules calls:" rg "useSchedules\s*\(\s*\{[^}]*transform\s*:" -g "*.{js,jsx,ts,tsx}" # Verify 'queryBuilder' usage in useSchedules calls, accounting for both explicit and shorthand syntax echo "Verifying 'queryBuilder' usage in useSchedules calls (both explicit and shorthand):" rg "useSchedules\s*\(\s*\{[^}]*(queryBuilder\s*:|queryBuilder\b)" -g "*.{js,jsx,ts,tsx}"Length of output: 1264
Script:
#!/bin/bash # Description: Identify 'useSchedules' calls that do not include the 'queryBuilder' property. # Search for 'useSchedules' calls without 'queryBuilder' echo "Searching for useSchedules calls missing 'queryBuilder':" rg "useSchedules\s*\(\s*\{(?![^}]*\bqueryBuilder\b)" -g "*.{js,jsx,ts,tsx}"Length of output: 479
Script:
#!/bin/bash # Description: Identify 'useSchedules' calls that do not include the 'queryBuilder' property. # Search for 'useSchedules' calls echo "Searching for useSchedules calls without 'queryBuilder':" rg "useSchedules\s*\(\s*\{[^}]*\}" -g "*.{js,jsx,ts,tsx}" | grep -L "queryBuilder"Length of output: 211
packages/desktop-client/src/components/accounts/Reconcile.tsx (1)
20-21
: Improved type definition for ReconcilingMessagePropsThe changes to the
ReconcilingMessageProps
type enhance flexibility and simplify the interface:
- Adding
accountId
allows for more versatile account identification.- Simplifying
balanceQuery
toQuery
type streamlines the prop structure.These modifications align well with the PR objectives and should improve code maintainability.
packages/desktop-client/e2e/accounts.test.js (2)
65-66
: Improved test reliability with explicit wait.The addition of
await accountPage.waitFor();
is a good practice. It ensures that the account page is fully loaded before proceeding with the test, which can prevent potential race conditions and improve test reliability.
Line range hint
1-165
: Overall improvement in test reliability.The changes in this file consistently improve test reliability by adding explicit waits after navigation or account creation. This aligns well with the PR objectives of enhancing test robustness. The overall structure and logic of the tests remain sound, with these additions addressing potential timing issues.
Consider applying similar wait patterns to other test files if they haven't been addressed already, to ensure consistent reliability across the entire test suite.
packages/desktop-client/src/components/transactions/TransactionList.jsx (3)
60-60
: LGTM: Addition ofisLoading
prop enhances loading state management.The new
isLoading
prop with a default value offalse
is a good addition. It aligns with the overall improvements in loading state management across components, as mentioned in the PR summary.
201-201
: LGTM: Propagation ofisLoading
prop to TransactionTable.Passing the
isLoading
prop to theTransactionTable
component is a good practice. It allows for consistent loading state management throughout the transaction list UI hierarchy.
Line range hint
1-241
: Overall assessment: Changes align well with PR objectives.The modifications to
TransactionList.jsx
successfully improve transaction handling efficiency and enhance loading state management. The changes are consistent with the PR objectives and the AI-generated summary. The code is well-structured, and the new features have been integrated smoothly.packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1)
187-187
: Excellent accessibility improvement!Adding the
aria-label="Account list"
to the View component is a great enhancement for screen reader users. This label provides clear context about the content within this section, making the application more accessible.packages/loot-core/src/client/queries.ts (1)
70-72
: LGTM! Consistent and clear changes.The renaming of
makeTransactionsQuery
totransactions
and the updated signature are good improvements. These changes maintain consistency with theaccountFilter
function while preserving the existing functionality.Also applies to: 75-79
packages/desktop-client/src/components/ManageRules.tsx (3)
Line range hint
1-368
: Summary: Minor refactoring with no functional changes.The changes in this file are part of a larger refactoring effort to improve code organization and consistency. The
useSchedules
hook import and usage have been updated without introducing any functional changes to theManageRules
component. The rest of the component remains unchanged, maintaining its original functionality.
12-12
: LGTM! Verify usage across the codebase.The import statement for
useSchedules
has been updated to use a centralized location in theloot-core
package. This change is likely part of a larger refactoring effort to improve code organization.To ensure consistency, please verify that all other components using the
useSchedules
hook have been updated similarly:#!/bin/bash # Search for any remaining imports of useSchedules from the old location rg "from '../hooks/useSchedules'" packages/desktop-client/src
116-116
: LGTM! VerifyuseSchedules
hook implementation.The destructuring of the
useSchedules
hook's return value has been simplified, directly assigningschedules
instead of using thedata
property. This change maintains backwards compatibility and doesn't affect the component's functionality.To ensure consistency, please verify the implementation of the
useSchedules
hook:✅ Verification successful
Destructuring of
useSchedules
hook verified and correct.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Display the content of the useSchedules hook cat packages/desktop-client/src/hooks/useSchedules.ts || cat packages/desktop-client/src/hooks/useSchedules.js || cat loot-core/client/data-hooks/schedules.ts || cat loot-core/client/data-hooks/schedules.jsLength of output: 432
Script:
#!/bin/bash # Search for the definition and usage of useSchedules hook rg 'useSchedules' --glob "*.ts" --glob "*.tsx" --glob "*.js" --glob "*.jsx"Length of output: 1780
Script:
#!/bin/bash # Display the content of the useSchedules hook cat packages/loot-core/src/client/data-hooks/schedules.tsxLength of output: 4688
packages/desktop-client/src/hooks/useSelected.tsx (2)
Line range hint
1-353
: Overall, great improvements to immutability and solid implementationThe changes to introduce readonly arrays enhance the robustness of the code by preventing accidental mutations. The overall implementation of the selection logic is solid and well-structured.
Consider the suggestions for removing the
// @ts-strict-ignore
comment and exploring performance optimizations for large lists to further improve the code quality and efficiency.Great work on maintaining and improving this complex piece of functionality!
55-56
: Excellent use of readonly arrays to promote immutability!The changes to make
items
andinitialSelectedIds
readonly in both theuseSelected
hook andSelectedProviderWithItems
component are a great improvement. This prevents accidental mutations of the input data, which can lead to more predictable and easier-to-debug code.To ensure that these changes don't cause any issues elsewhere in the codebase, let's verify the usage of
useSelected
andSelectedProviderWithItems
:This script will help us identify any potential issues with the usage of these functions in the codebase.
Also applies to: 312-313
packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3)
59-59
: LGTM! Proper implementation of the new prop.The addition of the
isLoading
prop with a default value offalse
is correctly implemented. This approach maintains backward compatibility and follows React best practices for handling optional props.
Line range hint
1-339
: Overall assessment: Well-implemented feature addition.The changes to add the
isLoading
prop to theSelectedTransactionsButton
component are well-implemented and consistent. They enhance the component's functionality by allowing it to handle loading states, which aligns with the PR objectives. The implementation maintains backward compatibility and follows React best practices.Key points:
- The
isLoading
prop is properly typed and optional.- A default value is provided to ensure backward compatibility.
- The prop is correctly passed to the child
SelectedItemsButton
component.These changes contribute to improving the overall user experience and component flexibility.
211-211
: LGTM! VerifySelectedItemsButton
implementation.The
isLoading
prop is correctly passed to theSelectedItemsButton
component, allowing it to reflect the loading state. This change enhances the component's functionality and improves user experience.To ensure full compatibility, let's verify the implementation of
SelectedItemsButton
:✅ Verification successful
Verified: The
isLoading
prop is correctly implemented in theSelectedTransactionsButton
component, ensuring the loading state is appropriately handled.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that SelectedItemsButton accepts and uses the isLoading prop rg -A 5 'SelectedItemsButton.*isLoading' packages/desktop-client/src/components/table.tsxLength of output: 91
Script:
#!/bin/bash # Description: Verify that SelectedTransactionsButton accepts and uses the isLoading prop rg 'isLoading\s*=' packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsxLength of output: 152
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)
33-33
: Excellent use of readonly type for immutability!The change from
ScheduleEntity[]
toreadonly ScheduleEntity[]
for theschedules
prop is a great improvement. This enforces immutability, aligning with React's best practices and preventing accidental modifications to the prop within the component. It enhances type safety and can help prevent bugs related to unintended mutations.
Line range hint
1-391
: Overall, excellent improvements to type safety!The changes in this file focus on enhancing type safety and enforcing immutability, which are great improvements. By making the
schedules
prop anditems
derived state readonly, you've reduced the risk of accidental mutations and aligned the code with React best practices. These modifications contribute to a more robust and maintainable codebase without altering the component's core functionality.Keep up the good work in leveraging TypeScript's features to improve code quality!
packages/loot-core/src/client/query-helpers.test.ts (11)
140-140
: LGTM: Updated function call to use object parameterThe
doQuery
function call has been correctly updated to use an object parameter, which aligns with the new function signatures forliveQuery
andpagedQuery
. This change improves code readability and allows for easier addition of parameters in the future.
156-159
: LGTM: Correctly updated function call with separated callback and optionsThe
doQuery
function call has been properly updated to use an object parameter, withonData
callback andoptions
correctly separated as properties. This change aligns with the new function signatures and improves code clarity.
174-177
: LGTM: Consistent update of function callThe
doQuery
function call has been consistently updated to use an object parameter, maintaining the pattern established in previous changes. This ensures uniformity across the test file.
200-202
: LGTM: Updated function call while preserving test logicThe
doQuery
function call has been correctly updated to use an object parameter, while still assigning the return value tolq
. This change maintains the existing test logic while adapting to the new function signature.
230-233
: LGTM: Consistent update of function callThe
doQuery
function call has been consistently updated to use an object parameter, maintaining the pattern established in previous changes. This ensures uniformity across the test file.
254-257
: LGTM: Consistent update of function callThe
doQuery
function call has been consistently updated to use an object parameter, maintaining the pattern established in previous changes. This ensures uniformity across the test file.
279-281
: LGTM: Updated function call while preserving test logicThe
doQuery
function call has been correctly updated to use an object parameter, while still assigning the return value tolq
. This change maintains the existing test logic, including the subsequentunsubscribe
operation, while adapting to the new function signature.
300-303
: LGTM: Correctly updated pagedQuery call with multiple callbacksThe
pagedQuery
function call has been properly updated to use an object parameter, with bothonData
andonPageData
callbacks correctly included as separate properties. This change aligns with the new function signature and improves code clarity.
361-363
: LGTM: Correctly updated pagedQuery call with callback and optionsThe
pagedQuery
function call has been properly updated to use an object parameter, with theonData
callback andoptions
correctly separated as properties. This change aligns with the new function signature and improves code clarity.
378-380
: LGTM: Consistent updates to pagedQuery calls across multiple test casesThe
pagedQuery
function calls have been consistently updated across multiple test cases to use object parameters. These changes correctly incorporate various combinations of callbacks and options, maintaining the existing test logic while adapting to the new function signature. This consistency ensures that all test cases are properly aligned with the updatedpagedQuery
implementation.Also applies to: 404-407, 440-443, 484-487
Line range hint
1-524
: Overall assessment: Successful adaptation of tests to new function signaturesThe changes in this file consistently update all
liveQuery
andpagedQuery
function calls to use object parameters, aligning with the new function signatures. These modifications:
- Improve code readability and maintainability.
- Preserve the original test coverage and intent.
- Provide a more flexible structure for potential future parameter additions.
The updates have been applied uniformly across all test cases, ensuring that the entire test suite remains consistent with the new implementation of the query helper functions.
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (1)
8-8
: Improved category management withuseCategories
hookThe addition of the
useCategories
hook and the modification of theoptions
destructuring represent a positive change in how categories are managed. This approach:
- Centralizes category data fetching.
- Potentially improves performance by avoiding unnecessary prop drilling.
- Enhances maintainability by separating concerns.
Also applies to: 158-159
packages/loot-core/src/client/data-hooks/filters.ts (1)
37-37
: Verify dependencies in 'useMemo' for consistent memoizationEnsure that the dependencies
[data, sort]
inuseMemo
are sufficient and thatdata
does not change on every render due to reference inequality. Ifdata
is a new array instance on each render, consider stabilizing it to prevent unnecessary recalculations.Run the following script to check if
data
maintains reference equality across renders:✅ Verification successful
Dependencies in
useMemo
are correctly specified anddata
maintains reference equality across renders.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for multiple instances where 'data' may be recreated. # Test: Search for all uses of 'useQuery' that might cause 'data' to change unnecessarily. rg "useQuery<.*>\(\s*\(\)\s*=>\s*q\(" # Expected: Instances where 'data' is stable and memoized properly.Length of output: 39
Script:
#!/bin/bash # Description: Check for instances where 'data' is recreated or modified and verify 'useMemo' dependencies. # Search for all instances where 'data' is reassigned or recreated rg 'data\s*=' packages/loot-core/src/client/data-hooks/filters.ts # Search for 'useMemo' usage to verify dependencies rg 'useMemo\s*\(\s*\(\s*\)\s*=>\s*sort\s*\(.*data.*\),\s*\[data,\s*sort\]\s*\)' packages/loot-core/src/client/data-hooks/filters.tsLength of output: 274
packages/loot-core/src/client/query-hooks.ts (2)
7-11
: Good use of a dedicated type for the query resultDefining
UseQueryResult<Response>
improves type safety and enhances code readability by clearly specifying the structure of the returned object fromuseQuery
.
14-16
: AllowingmakeQuery
to returnnull
increases flexibilityUpdating the
makeQuery
function to returnQuery | null
allows for optional queries, which can be useful in conditional scenarios.packages/loot-core/src/shared/query.ts (13)
1-5
: Definition ofObjectExpression
is appropriateThe recursive definition of
ObjectExpression
allows for nested expressions and provides flexibility for complex query expressions.
8-13
: Updates toQueryState
enhance flexibility and type safetyAdding
table
andtableOptions
toQueryState
, along with updating the types of expression arrays, improves the overall flexibility and type safety of the query system.
25-27
: Constructor change ensures thetable
property is requiredBy using
WithRequired<Partial<QueryState>, 'table'>
, the constructor now enforces that atable
is provided when creating a newQuery
instance, which is essential for query operations.
42-44
:filter
method correctly appends new expressionsThe
filter
method properly adds new filter expressions to the existing array without modifying the originalQuery
instance, maintaining immutability.
67-74
:select
method updates improve usabilityExpanding the
select
method to accept various input types, includingObjectExpression
,string
, and'*'
, enhances its usability and aligns it with typical query usage patterns.
84-87
:calculate
method correctly usesselect
for computationsThe
calculate
method leverages theselect
method to set up calculations, setting thecalculation
flag to distinguish it from regular selections.
Line range hint
90-95
:groupBy
method now accepts multiple expression typesAllowing
groupBy
to acceptObjectExpression
,string
, or arrays of these types provides greater flexibility in defining groupings within queries.
Line range hint
101-106
:orderBy
method enhancements increase ordering optionsBy accepting both
ObjectExpression
andstring
types,orderBy
now supports more complex ordering scenarios, improving query customization.
112-114
:limit
method enforces numeric inputSpecifying the
num
parameter as anumber
type in thelimit
method enhances type safety and prevents potential runtime errors.
116-118
:offset
method correctly specifies number typeDefining the
num
parameter as anumber
in theoffset
method ensures clarity and type safety in query pagination.
132-134
:options
method properly updatestableOptions
Accepting a
Record<string, unknown>
type foropts
allows theoptions
method to flexibly handle various table options.
145-148
:getPrimaryOrderBy
function now aligns with updated typesUpdating the function to accept a
Query
instance and anObjectExpression
fordefaultOrderBy
improves consistency with theQuery
class enhancements.
166-168
:q
function enforces thetable
type fromQueryState
By typing the
table
parameter asQueryState['table']
, theq
function ensures compatibility with theQuery
constructor and maintains type safety.packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (7)
30-35
: Efficient use ofuseCallback
for memoizingbaseTransactionsQuery
The
baseTransactionsQuery
function is correctly memoized withuseCallback
, which optimizes performance by avoiding unnecessary re-creations whencategory
ormonth
change.
39-41
: Proper initialization oftransactionsQuery
stateInitializing
transactionsQuery
with the result ofbaseTransactionsQuery()
ensures that the initial query aligns with the currentcategory
andmonth
.
42-49
: Appropriate use ofuseTransactions
hookUtilizing the
useTransactions
hook with the customtransactionsQuery
simplifies transaction management and improves code readability.
54-69
: Correct handling of synchronization events inuseEffect
The
useEffect
hook effectively listens for 'sync-event' and reloads transactions and payees as needed, ensuring the data remains up-to-date.
71-77
: Effective implementation of search with debounced callbackThe search functionality is efficiently managed using
useTransactionsSearch
anduseDebounceCallback
, which enhances performance by reducing unnecessary updates.
78-85
: Proper navigation logic inonOpenTransaction
The
onOpenTransaction
function correctly handles navigation by checking for preview transactions and usingnavigate
to open transactions when appropriate.
118-119
: Passing correct handlers toTransactionListWithBalances
The
onLoadMore
andonOpenTransaction
handlers are correctly passed to the component, ensuring proper functionality for loading more transactions and opening transactions.packages/loot-core/src/client/data-hooks/schedules.tsx (1)
7-9
: Correct addition of necessary React importsThe added imports for
useRef
,useMemo
, andtype PropsWithChildren
are appropriate and necessary for the updated code.packages/desktop-client/src/components/accounts/Balance.jsx (1)
159-166
: Ensure 'balanceQuery' is defined before creating 'balanceBinding'.In the
useMemo
hook,balanceBinding
depends onbalanceQuery
. IfbalanceQuery
isnull
orundefined
,balanceBinding
will have an undefinedquery
, which might cause issues in theCellValue
component.Please confirm that
balanceQuery
is always defined before rendering theBalances
component. If there's a possibility of it being undefined, consider adding a condition or providing a default value.packages/loot-core/src/shared/transactions.ts (1)
93-96
: Enhance immutability by addingreadonly
to parametersAdding
readonly
to thetransactions
parameters in these functions enhances immutability and prevents unintended mutations of the input arrays. This is a good practice that promotes safer and more predictable code.Also applies to: 110-113, 123-125, 139-139, 229-231, 248-250, 281-283, 306-308, 334-335, 355-357
packages/loot-core/src/client/query-helpers.ts (5)
10-22
: Improved type safety inliveQuery
function.The use of generics and structured options enhances type safety and clarity in the
liveQuery
function.
25-45
: EnhancedpagedQuery
function with better options handling.Applying generics and detailed option parameters improves the function's flexibility and usability.
48-57
: Addition of well-defined types improves code maintainability.Defining
Data
,Listener
,LiveQueryOptions
, andPagedQueryOptions
provides better type definitions, enhancing readability and maintainability.
60-238
: RefactoredLiveQuery
class enhances flexibility and robustness.The use of generics and refactored methods improves the class's flexibility and robustness.
🧰 Tools
🪛 Biome
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
Line range hint
247-425
: Improved pagination handling inPagedQuery
class.Updates to the
PagedQuery
class, including the use of generics and better asynchronous handling, enhance the pagination functionality and code clarity.🧰 Tools
🪛 Biome
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
.eslintrc.js (2)
164-169
: Approved: Addition of 'useQuery' to 'additionalHooks' in 'react-hooks/exhaustive-deps'This change ensures that the
exhaustive-deps
rule applies touseQuery
, which is appropriate and aligns with previous decisions to include onlyuseQuery
inadditionalHooks
.
167-167
:⚠️ Potential issueConsider refining the regex pattern to match 'useQuery' exactly
The current regex pattern
(useQuery)
might unintentionally match hook names that contain'useQuery'
as a substring (e.g.,useQueryData
). To ensure onlyuseQuery
is matched, consider changing the regex to^useQuery$
.Apply this diff to refine the regex pattern:
{ - additionalHooks: '(useQuery)', + additionalHooks: '^useQuery$', },⛔ Skipped due to learnings
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: .eslintrc.js:164-169 Timestamp: 2024-10-18T15:33:24.453Z Learning: When updating the `additionalHooks` pattern for `react-hooks/exhaustive-deps`, only `useQuery` should be added, not generalized patterns.
packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (1)
301-304
: Correct usage ofliveQuery
withonData
callbackThe updated
liveQuery
invocation with theonData
property aligns with the new API usage and ensures that transaction data is dispatched appropriately.packages/desktop-client/src/components/table.tsx (1)
842-843
: Verify thatButtonWithLoading
disables interaction whenisLoading
istrue
Ensure that when
isLoading
istrue
, theButtonWithLoading
component prevents user interactions to avoid duplicate submissions or unintended behaviors.Run the following script to verify how
ButtonWithLoading
handles theisLoading
prop:packages/desktop-client/src/components/modals/EditRuleModal.jsx (3)
312-317
: Correctly updateduseSchedules
hook usageThe
useSchedules
hook is now appropriately destructured to extractschedules
,statuses
(renamed toscheduleStatuses
), andisLoading
(renamed toisSchedulesLoading
). The usage ofuseCallback
forqueryBuilder
optimizes performance by preventing unnecessary recomputations whenid
doesn't change.
320-320
: Appropriate handling of loading stateReturning
null
whenisSchedulesLoading
istrue
ensures that the component doesn't render incomplete data while schedules are being fetched.
328-329
: Safe access toschedule
andstatus
After verifying that
schedules
is not empty, destructuring the first schedule and retrieving its status fromscheduleStatuses
is correctly implemented.packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)
769-776
: Correct handling of loading state in PayeeIcons componentThe addition of the loading check ensures that the component does not render schedule-related icons until schedules are loaded, preventing potential errors.
Line range hint
1765-1775
: 'isLoading' prop correctly integrated into TransactionTableInnerIncluding the
isLoading
prop inTransactionTableInner
ensures that the loading state is properly managed and passed down to child components.
Line range hint
1994-2005
: Passing 'isLoading' prop to Table componentThe
isLoading
prop is correctly passed to theTable
component, allowing it to display a loading indicator when transactions are being fetched.packages/desktop-client/src/components/accounts/Header.tsx (4)
65-65
: Ensuretransactions
beingreadonly
does not cause issuesThe
transactions
prop is now typed asreadonly TransactionEntity[]
. Please verify that no mutations are performed on this array within the component or its children, as this could lead to runtime errors or unexpected behavior.
296-296
: Confirm thatBalances
component functions correctly withoutisLoading
propThe
isLoading
prop passed to theBalances
component has been commented out. IfBalances
relies on this prop for displaying loading states, ensure that its removal does not affect the user experience.
199-199
: Check for potential issues with localsearch
stateThe
search
state is now managed locally in theAccountHeader
component, and thesearch
prop has been commented out. Ensure that this change does not break any parent-child synchronization or shared state that relies on thesearch
prop. Also, verify that any components or logic that depend on thesearch
prop are updated accordingly.Also applies to: 355-358, 155-155
450-450
: Verify the removal ofcanShowBalances
condition inAccountMenu
The
canShowBalances
prop and its associated logic have been commented out, and the "Toggle running balance" menu item is now always displayed. Previously, the display of this menu item was conditional. Confirm that showing this menu item in all cases is the intended behavior and does not lead to confusion or errors when the running balance cannot be displayed.Also applies to: 699-699, 722-737
if (scheduleData && scheduleData !== previousScheduleData) { | ||
// Kick off an async rules application | ||
const schedules = scheduleData.schedules.filter(s => | ||
isForPreview(s, scheduleData.statuses), | ||
); | ||
|
||
const baseTrans = schedules.map(schedule => ({ | ||
id: 'preview/' + schedule.id, | ||
payee: schedule._payee, | ||
account: schedule._account, | ||
amount: schedule._amount, | ||
date: schedule.next_date, | ||
schedule: schedule.id, | ||
})); | ||
const baseTrans = schedules.map(schedule => ({ | ||
id: 'preview/' + schedule.id, | ||
payee: schedule._payee, | ||
account: schedule._account, | ||
amount: schedule._amount, | ||
date: schedule.next_date, | ||
schedule: schedule.id, | ||
})); | ||
|
||
Promise.all( | ||
baseTrans.map(transaction => send('rules-run', { transaction })), | ||
).then(newTrans => { | ||
const withDefaults = newTrans.map(t => ({ | ||
...t, | ||
category: scheduleData.statuses.get(t.schedule), | ||
Promise.all( | ||
baseTrans.map(transaction => send('rules-run', { transaction })), | ||
).then(newTrans => { | ||
const withDefaults = newTrans.map(t => ({ | ||
...t, | ||
category: scheduleData.statuses.get(t.schedule), | ||
schedule: t.schedule, | ||
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({ | ||
...st, | ||
id: 'preview/' + st.id, | ||
schedule: t.schedule, | ||
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({ | ||
...st, | ||
id: 'preview/' + st.id, | ||
schedule: t.schedule, | ||
})), | ||
})); | ||
setPreviewTransactions(ungroupTransactions(withDefaults)); | ||
if (collapseTransactions) { | ||
collapseTransactions(withDefaults.map(t => t.id)); | ||
} | ||
}); | ||
} | ||
|
||
return previewTransactions; | ||
})), | ||
})); | ||
setPreviewTransactions(ungroupTransactions(withDefaults)); | ||
if (collapseTransactions) { | ||
collapseTransactions(withDefaults.map(t => t.id)); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with suggestions: Improved efficiency and data structure
The changes to the main hook logic, particularly the check for updated scheduleData
, improve the hook's efficiency. The updated transaction structure provides more comprehensive information, which is beneficial.
However, there's room for improvement in error handling:
Consider adding error handling to the Promise.all call:
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
)
.then(newTrans => {
// ... existing logic ...
})
.catch(error => {
console.error('Error applying rules:', error);
// Consider setting an error state or calling an error callback
});
This addition will prevent unhandled promise rejections and provide better debugging information if an error occurs during rule application.
/** | ||
* @deprecated Please use `usePreviewTransactions` hook from `loot-core/client/data-hooks/transactions` instead. | ||
*/ | ||
export function usePreviewTransactions( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Deprecated usePreviewTransactions
hook is still in use
The usePreviewTransactions
hook is still being used in the following files:
packages/desktop-client/src/components/accounts/Account.tsx
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx
Please migrate these usages to the new hook from loot-core/client/data-hooks/transactions
before removing the deprecated hook.
🔗 Analysis chain
Verify usage of deprecated hook
The usePreviewTransactions
hook is marked as deprecated, and static analysis suggests it might not be in use.
Please verify if this hook is still needed:
- If it's no longer used, consider removing it entirely.
- If it's still in use, ensure that all occurrences are migrated to the new hook mentioned in the deprecation comment.
You can use the following script to check for any remaining usage:
Based on the results, decide whether to remove this hook or update the deprecation timeline.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for usage of deprecated usePreviewTransactions hook
echo "Searching for usePreviewTransactions usage:"
rg -p "usePreviewTransactions" --type ts --type tsx --glob '!packages/desktop-client/src/hooks/usePreviewTransactions.ts'
echo "Searching for new hook usage:"
rg -p "usePreviewTransactions" packages/loot-core/src/client/data-hooks/transactions.ts
Length of output: 479
Script:
#!/bin/bash
# Description: Corrected script to check for usage of deprecated usePreviewTransactions hook
echo "Searching for usePreviewTransactions usage:"
rg -e "usePreviewTransactions" --type ts --type tsx --glob '!packages/desktop-client/src/hooks/usePreviewTransactions.ts'
echo "Searching for new hook usage:"
rg -e "usePreviewTransactions" packages/loot-core/src/client/data-hooks/transactions.ts
Length of output: 476
Script:
#!/bin/bash
# Description: Corrected script to check for usage of deprecated usePreviewTransactions hook
echo "Searching for usePreviewTransactions usage in .ts and .tsx files:"
rg -e "usePreviewTransactions" --glob "*.ts" --glob "*.tsx" --glob '!packages/desktop-client/src/hooks/usePreviewTransactions.ts'
echo "Searching for new hook usage:"
rg -e "usePreviewTransactions" packages/loot-core/src/client/data-hooks/transactions.ts
Length of output: 1056
🧰 Tools
🪛 GitHub Check: lint
[warning] 19-19:
exported declaration 'usePreviewTransactions' not used within other modules
const sort = useCallback((filters: TransactionFilterEntity[]) => { | ||
return filters.toSorted((a, b) => | ||
a.name | ||
.trim() | ||
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }), | ||
); | ||
} | ||
}, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace 'toSorted' with a more widely supported sorting method
The Array.prototype.toSorted
method is a recent addition and may not be supported in all environments, potentially leading to compatibility issues. Consider using slice().sort()
or spreading the array before sorting to ensure broader compatibility.
Apply this diff to replace toSorted
:
-return filters.toSorted((a, b) =>
+return filters.slice().sort((a, b) =>
Or using the spread operator:
-return filters.toSorted((a, b) =>
+return [...filters].sort((a, b) =>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const sort = useCallback((filters: TransactionFilterEntity[]) => { | |
return filters.toSorted((a, b) => | |
a.name | |
.trim() | |
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }), | |
); | |
} | |
}, []); | |
const sort = useCallback((filters: TransactionFilterEntity[]) => { | |
return filters.slice().sort((a, b) => | |
a.name | |
.trim() | |
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }), | |
); | |
}, []); |
let isUnmounted = false; | ||
let live: null | LiveQuery<Response> = liveQuery<Response>(query, { | ||
onData: data => { | ||
if (!isUnmounted) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use useRef
for isUnmounted
flag to prevent stale closure issues
Using a local variable isUnmounted
inside useEffect
can lead to stale closures in certain scenarios. Consider using useRef
to ensure the flag remains consistent across renders.
Apply this diff to utilize useRef
:
+ const isUnmounted = useRef(false);
useEffect(() => {
- let isUnmounted = false;
// ...
liveQuery<Response>(query, {
onData: data => {
- if (!isUnmounted) {
+ if (!isUnmounted.current) {
setData(data);
setIsLoading(false);
}
},
});
return () => {
- isUnmounted = true;
+ isUnmounted.current = true;
live?.unsubscribe();
live = null;
};
}, [query]);
Committable suggestion was skipped due to low confidence.
unfilter(exprs?: Array<keyof ObjectExpression>) { | ||
// Remove all filters if no arguments are passed | ||
if (!exprs) { | ||
return new Query({ | ||
...this.state, | ||
filterExpressions: [], | ||
}); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential issue in unfilter
when handling expressions with multiple keys
The unfilter
method assumes each expression in filterExpressions
has only one key by using Object.keys(expr)[0]
. If a filter expression contains multiple keys, it may not be correctly removed, leading to unexpected behavior.
Consider modifying the unfilter
method to handle expressions with multiple keys. One possible solution is to compare the entire keys array or use a more robust matching mechanism.
unfilter(exprs?: Array<keyof ObjectExpression>) {
// Remove all filters if no arguments are passed
if (!exprs) {
return new Query({
...this.state,
filterExpressions: [],
});
}
const exprSet = new Set(exprs);
return new Query({
...this.state,
filterExpressions: this.state.filterExpressions.filter(expr => {
- return !exprSet.has(Object.keys(expr)[0]);
+ return !Object.keys(expr).some(key => exprSet.has(key));
}),
});
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
unfilter(exprs?: Array<keyof ObjectExpression>) { | |
// Remove all filters if no arguments are passed | |
if (!exprs) { | |
return new Query({ | |
...this.state, | |
filterExpressions: [], | |
}); | |
} | |
unfilter(exprs?: Array<keyof ObjectExpression>) { | |
// Remove all filters if no arguments are passed | |
if (!exprs) { | |
return new Query({ | |
...this.state, | |
filterExpressions: [], | |
}); | |
} | |
const exprSet = new Set(exprs); | |
return new Query({ | |
...this.state, | |
filterExpressions: this.state.filterExpressions.filter(expr => { | |
return !Object.keys(expr).some(key => exprSet.has(key)); | |
}), | |
}); | |
} |
onlySync; | ||
query; | ||
export class LiveQuery<TResponse = unknown> { | ||
private _unsubscribeSyncEvent: () => void | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarify type annotation of _unsubscribeSyncEvent
to avoid confusion.
Currently, _unsubscribeSyncEvent
is typed as () => void | null
, which can be confusing because it's unclear if null
refers to the function itself or its return value. It would be clearer to use (() => void) | null
to indicate that _unsubscribeSyncEvent
is either a function that returns void or null
.
Apply this diff to clarify the type annotation:
-private _unsubscribeSyncEvent: () => void | null;
+private _unsubscribeSyncEvent: (() => void) | null;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
private _unsubscribeSyncEvent: () => void | null; | |
private _unsubscribeSyncEvent: (() => void) | null; |
🧰 Tools
🪛 Biome
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
{ | ||
onData: data => | ||
dispatch({ type: 'set-transactions', transactions: data }), | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure unsubscribe
is defined before use in cleanup function
In the useEffect
starting at line 323, unsubscribe
is assigned within a Promise's then
block. If the component unmounts before the Promise resolves, unsubscribe
may be undefined
, which could lead to errors in the cleanup function when attempting to call it.
To prevent this potential issue, initialize unsubscribe
to a no-op function to ensure it's always callable:
useEffect(() => {
let current = true;
- let unsubscribe;
+ let unsubscribe = () => {};
if (state.schedule && state.transactionsMode === 'matched') {
// existing code...
}
return () => {
current = false;
- if (unsubscribe) {
unsubscribe();
- }
};
}, [state.schedule, state.transactionsMode, state.fields]);
Committable suggestion was skipped due to low confidence.
@@ -816,13 +816,15 @@ type SelectedItemsButtonProps<Name extends string> = { | |||
name: ((count: number) => string) | string; | |||
items: MenuItem<Name>[]; | |||
onSelect: (name: Name, items: string[]) => void; | |||
isLoading: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make isLoading
optional in SelectedItemsButtonProps
to prevent breaking changes
Adding isLoading
as a required property in SelectedItemsButtonProps
may lead to TypeScript errors in components that use SelectedItemsButton
without specifying this new prop. Since you've provided a default value of false
for isLoading
in the function signature, you can make it optional in the type definition to maintain backward compatibility.
Apply this diff to make isLoading
optional:
- isLoading: boolean;
+ isLoading?: boolean;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
isLoading: boolean; | |
isLoading?: boolean; |
isLoading={isLoading} | ||
getTransaction={id => transactions.find(t => t.id === id)} | ||
onShow={onShowTransactions} | ||
onDuplicate={onBatchDuplicate} | ||
onDelete={onBatchDelete} | ||
onEdit={onBatchEdit} | ||
onLinkSchedule={onBatchLinkSchedule} | ||
onUnlinkSchedule={onBatchUnlinkSchedule} | ||
onCreateRule={onCreateRule} | ||
onSetTransfer={onSetTransfer} | ||
onScheduleAction={onScheduleAction} | ||
showMakeTransfer={showMakeTransfer} | ||
onMakeAsSplitTransaction={onMakeAsSplitTransaction} | ||
onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize transaction lookup in getTransaction
function
The getTransaction
prop is receiving a function that searches the transactions
array for a transaction by ID. Since transactions
is now a readonly
array and could be large, consider optimizing this lookup by using a Map
or memoization to improve performance.
@@ -18,7 +19,7 @@ | |||
import { useLocalPref } from '../../hooks/useLocalPref'; | |||
import { useSplitsExpanded } from '../../hooks/useSplitsExpanded'; | |||
import { useSyncServerStatus } from '../../hooks/useSyncServerStatus'; | |||
import { AnimatedLoading } from '../../icons/AnimatedLoading'; | |||
// import { AnimatedLoading } from '../../icons/AnimatedLoading'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove unused import of AnimatedLoading
The import statement for AnimatedLoading
is commented out. If AnimatedLoading
is no longer used in this file, consider removing the import statement entirely to keep the code clean.
Apply this diff to remove the commented-out import:
-// import { AnimatedLoading } from '../../icons/AnimatedLoading';
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// import { AnimatedLoading } from '../../icons/AnimatedLoading'; |
fddd46f
to
0ba2d54
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (25)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)
20-22
: LGTM: Improved hook usage with query optimization.The changes enhance the
useSchedules
hook usage by adding a memoized query and handling the loading state. This aligns with best practices for React hooks and optimizes performance.Consider extracting the memoized query to a separate variable for improved readability:
+const schedulesQuery = useMemo(() => q('schedules').select('*'), []); const { schedules = [], isLoading } = useSchedules({ - query: useMemo(() => q('schedules').select('*'), []), + query: schedulesQuery, });
24-26
: LGTM: Added loading state handling.The addition of the loading state check is a good practice, preventing the component from rendering with incomplete data.
Consider adding a loading indicator for a better user experience:
if (isLoading) { - return null; + return <LoadingSpinner />; // Assuming you have a LoadingSpinner component }packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (2)
36-40
: LGTM: ImproveduseSchedules
hook usage with query optimization.The changes to the
useSchedules
hook usage are well-implemented:
- Destructuring
isLoading
(renamed toisSchedulesLoading
) andschedules
improves readability.- Using a memoized
query
parameter withuseMemo
is a good optimization.Minor suggestion: Consider adding a comment explaining the purpose of the query, especially if it's a complex one.
47-48
: LGTM: Improved robustness with optional chaining.The use of optional chaining when accessing schedule properties is a good practice:
- It prevents potential runtime errors when properties are undefined.
- The code is more concise and readable.
Suggestion: Consider adding a fallback value for the schedule name and date to improve the user experience when data is unavailable.
Example:
title={<ModalTitle title={schedule?.name || 'Untitled Schedule'} shrinkOnOverflow />} // ... {format(schedule?.next_date || new Date(), 'MMMM dd, yyyy')}Also applies to: 54-54, 68-68
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (1)
51-67
: LGTM with a minor suggestion:useEffect
for sync events updated correctly.The
useEffect
hook for handling sync events has been refactored effectively:
- It now uses the
reloadTransactions
function from theuseTransactions
hook.- The dependency array correctly includes both
dispatch
andreloadTransactions
.However, there's a minor improvement that could be made:
Consider removing the optional chaining operator (
?.
) when callingreloadTransactions()
on line 59. SincereloadTransactions
is destructured from theuseTransactions
hook, it should always be defined. The optional chaining is unnecessary and might hide potential issues ifreloadTransactions
is unexpectedly undefined.- reloadTransactions?.(); + reloadTransactions();packages/loot-core/src/client/data-hooks/schedules.tsx (3)
26-31
: Improved function signature enhances flexibility and type safety.The updates to the
loadStatuses
function signature are beneficial:
- Addition of
onError
callback improves error handling.upcomingLength
parameter allows for more flexible data processing.readonly
type forschedules
enforces immutability.These changes enhance the function's flexibility and type safety.
Consider adding type annotations for
onData
andonError
callbacks for improved type safety:function loadStatuses( schedules: readonly ScheduleEntity[], onData: (data: ScheduleStatuses) => void, onError: (error: Error) => void, upcomingLength: string ): LiveQuery<TransactionEntity>
54-126
: Significant improvements to useSchedules hook structure and type safety.The changes to the
useSchedules
hook are substantial and beneficial:
- New type definitions (
UseSchedulesProps
,ScheduleData
,UseSchedulesResult
) improve type safety and readability.- Use of
useRef
for query references ensures proper cleanup and prevents memory leaks.- The effect cleanup now properly unsubscribes from both queries.
- The hook returns more detailed information including loading state and error.
These changes greatly enhance the hook's functionality and maintainability.
Consider adding a check to prevent unnecessary state updates when the component is unmounted:
if (!isUnmounted) { setError(error); }This can be applied to the
setError
call in theonError
callback of theliveQuery
call.
154-176
: Well-structured accountSchedulesQuery function with flexible filtering.The new
accountSchedulesQuery
function is a valuable addition:
- It provides a flexible way to build queries for schedules based on account ID.
- Special cases like 'uncategorized' accounts are handled appropriately.
- The use of
accountFilter
aligns with the import changes at the beginning of the file.This function enhances the querying capabilities for schedules.
Consider extracting the account types into a union type for better readability and maintainability:
type AccountQueryType = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized'; export function accountSchedulesQuery(accountId?: AccountQueryType) { // ... rest of the function }packages/desktop-client/src/components/accounts/Reconcile.tsx (3)
20-21
: LGTM! Consider adding JSDoc comments for improved documentation.The changes to
ReconcilingMessageProps
look good. The addition ofaccountId
and simplification ofbalanceQuery
align well with the PR objectives.Consider adding JSDoc comments to these props for better documentation:
type ReconcilingMessageProps = { /** The ID of the account being reconciled */ accountId: AccountEntity['id'] | string; /** The query used to fetch the balance */ balanceQuery: Query; // ... other props };
28-28
: LGTM! Consider using a type assertion for improved type safety.The changes in the
ReconcilingMessage
component correctly implement the newaccountId
prop and update theuseSheetValue
hook usage. The query simplification is a good improvement.For improved type safety, consider using a type assertion for the
name
property inuseSheetValue
:name: `balance-query-${accountId}-cleared` as const,This ensures that the string literal type is preserved, which can be helpful for type checking in other parts of the application.
Also applies to: 35-38
Line range hint
124-129
: UpdateReconcileMenu
to align with newbalanceQuery
structure.The
ReconcileMenu
component hasn't been updated to reflect the changes made inReconcilingMessage
. This could lead to inconsistencies in howbalanceQuery
is used across the application.Consider updating the
ReconcileMenu
component to use the newbalanceQuery
structure:const balanceQuery = queries.accountBalance(account); const clearedBalance = useSheetValue<'account', `balance-${string}-cleared`>({ name: `balance-${account.id}-cleared` as const, value: null, query: balanceQuery.filter({ cleared: true }), });This change would ensure consistency with the updates made in
ReconcilingMessage
and align with the overall PR objectives.packages/desktop-client/src/components/accounts/Balance.jsx (2)
Line range hint
45-76
: Approve changes with a minor suggestion.The migration from
account
toaccountId
and the introduction ofuseCachedSchedules
align well with the PR objectives. The new implementation improves performance and correctly handles schedule balances for different accounts.However, there's a minor issue that was previously flagged:
Initialize
scheduleBalance
to 0 instead of null to prevent potentialNaN
issues in calculations:-let scheduleBalance = null; +let scheduleBalance = 0;This change ensures correct calculations when adding to
scheduleBalance
.
Line range hint
147-230
: Approve changes with a minor suggestion for CSS.The modifications to the
Balances
component significantly improve its functionality and align well with the PR objectives:
- The new props (
accountId
,balanceQuery
, etc.) enhance the component's flexibility.- Using
ButtonWithLoading
improves user experience during data fetching.- The
useMemo
hook forbalanceBinding
optimizes performance.- Conditional rendering of
MoreBalances
andFilteredBalance
is more explicit and maintainable.However, there's a minor suggestion for the CSS:
Consider using object notation for CSS properties that use kebab-case in JavaScript:
className={css({ paddingTop: 1, paddingBottom: 1, [`& svg`]: { width: 10, height: 10, marginLeft: 10, color: theme.pillText, - transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)', + transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0deg)', opacity: selectedItems.size > 0 || showExtraBalances ? 1 : 0, }, [`&[data-hovered] svg`]: { opacity: 1, }, })}This change ensures consistency in the rotation values and improves readability.
packages/loot-core/src/client/data-hooks/transactions.ts (2)
91-180
: LGTM: Well-implemented preview transactions hook with a minor optimization suggestion.The
usePreviewTransactions
hook is well-implemented, handling various states appropriately and usinguseMemo
for optimization. The error handling is comprehensive.A minor optimization suggestion:
Consider memoizing the
previewTransactions
state setter function to avoid unnecessary re-renders:- const [previewTransactions, setPreviewTransactions] = useState< - TransactionEntity[] - >([]); + const [previewTransactions, setPreviewTransactions] = useState<TransactionEntity[]>([]); + const memoizedSetPreviewTransactions = useCallback((transactions: TransactionEntity[]) => { + setPreviewTransactions(ungroupTransactions(transactions)); + }, []); // ... in the effect - setPreviewTransactions(ungroupTransactions(withDefaults)); + memoizedSetPreviewTransactions(withDefaults);This change is optional and may provide a slight performance boost in scenarios with frequent updates.
182-232
: LGTM: Well-implemented search hook with a suggestion for enhanced type safety.The
useTransactionsSearch
hook and theisForPreview
helper function are well-implemented. The use ofdebounce
anduseCallback
in the search hook is appropriate for optimizing performance.To enhance type safety, consider adding a type for the
searchText
parameter in theupdateSearchQuery
callback:const updateSearchQuery = debounce( useCallback( - (searchText: string) => { + (searchText: string | undefined) => { if (searchText === '') { resetQuery(); setIsSearching(false); - } else if (searchText) { + } else if (searchText && searchText.length > 0) { updateQuery(previousQuery => queries.transactionsSearch(previousQuery, searchText, dateFormat), ); setIsSearching(true); } }, [dateFormat, resetQuery, updateQuery], ), delayMs, );This change ensures that the function handles potential
undefined
inputs correctly and only performs a search when there's actual text to search for.packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (4)
59-63
: Improved schedule querying and optimizationThe replacement of
useDefaultSchedulesQueryTransform
with a memoizedaccountSchedulesQuery
is a good optimization. It ensures the query is only recreated whenaccountId
changes. TheSchedulesProvider
update to use aquery
prop instead of atransform
prop aligns well with the new data fetching patterns.Consider updating the component props type definition to be more specific:
type AccountTransactionsProps = { readonly account?: AccountEntity; readonly accountId?: AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized'; readonly accountName: AccountEntity['name'] | string; };This change would make the component's interface more clear and type-safe.
Also applies to: 81-87
Line range hint
106-155
: Performance optimization with useCallbackThe use of
useCallback
foronSave
,onEditNotes
,onCloseAccount
, andonReopenAccount
functions is a good practice for performance optimization. This can help prevent unnecessary re-renders in child components that receive these callbacks as props.The memoization of
onClick
and its expanded dependency list is also a positive change, ensuring the function is correctly updated when its dependencies change.For consistency, consider also wrapping the
onSaveNotes
function withuseCallback
:const onSaveNotes = useCallback(async (id: string, notes: string) => { await send('notes-save', { id, note: notes }); }, []);This would make the pattern consistent across all callback functions in this component.
224-240
: Improved transaction querying and state managementThe introduction of
baseTransactionsQuery
and the use ofuseState
fortransactionsQuery
significantly improves how transaction data is queried and managed. TheuseTransactions
hook provides a clean way to fetch and manage transaction data, including loading states and pagination.These changes enhance the component's flexibility and performance. The use of
useCallback
forbaseTransactionsQuery
ensures query stability across renders, which is a good optimization.Consider adding error handling to the
useTransactions
hook usage:const { transactions, isLoading, error, reload: reloadTransactions, loadMore: loadMoreTransactions, } = useTransactions({ query: transactionsQuery, }); // Then, handle the error state in your component if (error) { // Render an error message or trigger an error boundary }This would make the component more robust in handling potential data fetching errors.
277-305
: Enhanced search functionality and transaction handlingThe introduction of the
useTransactionsSearch
hook and the updates to theonOpenTransaction
function represent significant improvements:
The
useTransactionsSearch
hook likely provides a more efficient and reusable way to handle transaction searches, improving the overall performance and user experience.The
onOpenTransaction
function now correctly differentiates between regular and preview transactions, ensuring appropriate actions are taken for each type.These changes enhance the component's functionality and user interaction.
To improve code readability, consider extracting the preview transaction handling logic into a separate function:
const handlePreviewTransaction = useCallback((transactionId: string) => { const parts = transactionId.split('/'); dispatch( pushModal('scheduled-transaction-menu', { transactionId, onPost: async () => { await send('schedule/post-transaction', { id: parts[1] }); dispatch(collapseModals('scheduled-transaction-menu')); }, onSkip: async () => { await send('schedule/skip-next-date', { id: parts[1] }); dispatch(collapseModals('scheduled-transaction-menu')); }, }) ); }, [dispatch]); // Then in onOpenTransaction: if (isPreviewId(transaction.id)) { handlePreviewTransaction(transaction.id); } else { navigate(`/transactions/${transaction.id}`); }This separation of concerns would make the
onOpenTransaction
function more readable and maintainable.packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (1)
243-243
: Correct adjustment toparseCategoryFields
function.The function signature change aligns with the new structure of categories obtained from the
useCategories
hook. This maintains the existing functionality while adapting to the new data structure.Consider optimizing this function by using a Map for O(1) lookup:
function parseCategoryFields(trans, categories) { const categoryMap = new Map(categories.map(cat => [cat.name, cat.id])); return categoryMap.get(trans.category) || null; }This approach would be more efficient, especially for large category lists.
packages/desktop-client/src/components/table.tsx (1)
819-819
: LGTM! Good addition of loading state.The introduction of the
isLoading
prop and the use ofButtonWithLoading
component improves user feedback during asynchronous operations. The default value offalse
forisLoading
ensures backward compatibility.Consider using a more specific type for the
isLoading
prop in theSelectedItemsButtonProps
interface:- isLoading: boolean; + isLoading?: boolean;This change would make the type definition consistent with the default value in the function signature.
Also applies to: 827-827, 842-856
packages/desktop-client/src/components/modals/EditRuleModal.jsx (2)
306-323
: Great refactoring of the ScheduleDescription component.The changes to use the new
useSchedules
hook are well-implemented. The destructuring ofschedules
,statuses
, andisLoading
improves code readability and aligns with the PR objectives.Consider adding an error state check for better user experience:
if (isSchedulesLoading) { return null; } + if (error) { + return <View style={{ flex: 1 }}>Error loading schedule: {error.message}</View>; + } if (schedules.length === 0) { return <View style={{ flex: 1 }}>{id}</View>; }This addition would handle potential errors from the
useSchedules
hook, providing better feedback to the user.
Line range hint
1-1000
: Overall, the changes improve the component's structure and align with PR objectives.The primary modification in this file is the refactoring of the
ScheduleDescription
component to use the newuseSchedules
hook. This change aligns well with the PR's goal of migrating to functional components and utilizing the new hook.The rest of the file remains largely unchanged, which is good as it minimizes the risk of introducing unintended side effects. The refactoring improves code readability and maintainability without altering the overall functionality of the
EditRuleModal
component.To further improve the code:
- Consider adding JSDoc comments for the main components and functions to enhance documentation.
- Evaluate if any of the nested components (e.g.,
ConditionEditor
,ActionEditor
) could be moved to separate files to reduce the overall file size and improve modularity.packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)
Line range hint
1765-1775
: Enhanced loading state handling in TransactionTableInnerThe addition of the
isLoading
prop to theTransactionTableInner
component and its propagation to theTable
component is a positive change. This allows for better management of loading states throughout the transaction table, potentially improving user experience by providing visual feedback during data fetching or processing.For consistency, consider destructuring the
isLoading
prop along with the other props at the beginning of the function, like this:function TransactionTableInner({ isLoading, tableNavigator, tableRef, listContainerRef, dateFormat = 'MM/dd/yyyy', newNavigator, renderEmpty, onScroll, ...props }) { // ... rest of the component }This would align with the component's existing style and make it clearer that
isLoading
is an expected prop.Also applies to: 1994-2005
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)
Line range hint
261-277
: Ensure type consistency when passingitems
to theTable
componentThe
items
array includes bothScheduleEntity
andCompletedScheduleItem
types, but it is being cast toScheduleEntity[]
when passed to theTable
component. This may lead to type safety issues or runtime errors if theTable
component expects onlyScheduleEntity
objects.Consider updating the
Table
component's typing to acceptSchedulesTableItem[]
instead of castingitems
toScheduleEntity[]
. Alternatively, adjust theitems
array to include onlyScheduleEntity
objects before passing it to theTable
component.Apply this diff to pass
items
without casting:- items={items as ScheduleEntity[]} + items={items}And ensure the
Table
component can handleSchedulesTableItem[]
appropriately.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (23)
- packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
- packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
- packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
- packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
- packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
- packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
- packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
- packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
- packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
- packages/desktop-client/src/components/table.tsx (4 hunks)
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
- packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
- packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
- packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
- packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/desktop-client/src/components/ManageRules.tsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/schedules/index.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (2)
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-10-08T15:46:15.739Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-09-27T14:15:46.637Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (48)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (2)
Line range hint
1-38
: Summary: Successfully migrated to functional component with new hook usage.The
ScheduleValue
component has been successfully updated to use the newuseSchedules
hook, aligning with the PR objectives. Key improvements include:
- Optimized query creation with
useMemo
.- Proper handling of loading states.
- Default value for
schedules
to prevent potential errors.However, there's a TODO item regarding type coercion that should be addressed in a follow-up task.
Overall, the changes improve the component's performance and maintainability.
Line range hint
32-38
: Address TODO: Investigate type coercion in schedule._payee.The TODO comment highlights a potential type issue with
schedule._payee
. This should be investigated and addressed to improve type safety.Let's verify the usage of
schedule._payee
across the codebase:Based on the results, consider refactoring to use
schedule._payee.id
if appropriate, or add proper type assertions/guards to ensure type safety.Would you like assistance in refactoring this part of the code once the investigation is complete?
packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3)
2-2
: LGTM: Import statements updated correctly.The addition of
useMemo
and the import ofq
from the query module align well with the changes in how schedules are fetched and optimized.Also applies to: 9-9
43-45
: LGTM: Proper handling of loading state.The addition of the loading state check is a good practice. Returning
null
while the schedules are loading prevents rendering incomplete or incorrect content.
Line range hint
1-124
: Overall assessment: Well-implemented changes improving component robustness and alignment with modern practices.The modifications to
ScheduledTransactionMenuModal.tsx
successfully achieve the following:
- Improved data fetching with the updated
useSchedules
hook.- Better handling of loading states and potential undefined values.
- Use of React hooks and modern JavaScript features (optional chaining, useMemo).
These changes align well with the PR objectives of migrating to functional components and using the new hooks. The code is now more robust and maintainable.
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (7)
1-1
: LGTM: Import statements updated correctly.The new imports for
useTransactions
anduseTransactionsSearch
are correctly added, aligning with the migration to functional components and the use of new hooks as per the PR objectives.Also applies to: 5-8
28-35
: LGTM:baseTransactionsQuery
function refactored correctly.The
baseTransactionsQuery
function has been appropriately refactored:
- It's now memoized using
useCallback
, which can improve performance.- The dependency array correctly includes
category
andmonth
.- The query structure is aligned with the component's purpose.
37-47
: LGTM: Transaction state management refactored effectively.The state management for transactions has been significantly improved:
- The
useState
hook is used to manage thetransactionsQuery
state.- The
useTransactions
custom hook is implemented correctly, simplifying the logic for fetching and managing transactions.- The destructuring of values from
useTransactions
provides clear access to necessary functions and data (transactions
,isLoading
,loadMore
,reload
).This refactoring aligns well with React best practices for functional components and the PR objectives.
69-73
: LGTM:useTransactionsSearch
hook implemented correctly.The
useTransactionsSearch
hook has been implemented effectively:
- It manages the search functionality and updates the transactions query as needed.
- The
updateQuery
andresetQuery
callbacks are properly defined, using thesetTransactionsQuery
state setter andbaseTransactionsQuery
function respectively.- The
dateFormat
is correctly passed to the hook.This implementation aligns well with the refactoring goals and simplifies the search functionality management.
75-82
: LGTM:onOpenTransaction
callback refactored effectively.The
onOpenTransaction
callback has been well-refactored:
- It's now memoized using
useCallback
, which can help optimize performance.- The dependency array correctly includes the
navigate
function.- The logic for handling transactions has been simplified while maintaining the necessary check for preview transactions.
- The comment on line 77 provides useful context about the previous implementation for native app handling of preview transactions.
This refactoring aligns well with React best practices and the overall goals of the PR.
115-116
: LGTM:TransactionListWithBalances
props updated correctly.The props passed to the
TransactionListWithBalances
component have been updated appropriately:
- The new
isLoading
prop has been added, allowing for better handling of loading states.- The
onLoadMore
prop now correctly uses theloadMoreTransactions
function from theuseTransactions
hook.- The
onOpenTransaction
prop uses the memoizedonOpenTransaction
callback.These changes are consistent with the new hook-based approach for managing transactions and align well with the overall refactoring goals.
Line range hint
1-127
: Overall assessment: Excellent refactoring of theCategoryTransactions
component.The migration of the
CategoryTransactions
component to a functional component using the newuseTransactions
anduseTransactionsSearch
hooks has been executed very well. The changes align perfectly with the PR objectives and demonstrate a good understanding of React best practices. Key improvements include:
- Simplified state management using custom hooks.
- Improved performance through the use of
useCallback
where appropriate.- Cleaner and more maintainable code structure.
- Consistent use of the new hook-based approach throughout the component.
The only minor suggestion is to remove the optional chaining operator when calling
reloadTransactions()
. Overall, this refactoring significantly enhances the component's quality and maintainability.packages/loot-core/src/client/data-hooks/schedules.tsx (5)
7-20
: Improved imports enhance type safety and align with codebase updates.The changes to the import statements are well-considered:
- Addition of
useRef
suggests better management of mutable values.PropsWithChildren
import improves type safety for components with children.- New entity type imports (
TransactionEntity
,ScheduleEntity
,AccountEntity
) enhance type checking.- The switch to
accountFilter
aligns with updates in other parts of the codebase.These changes contribute to better type safety and code consistency.
32-50
: Improved data handling and error management in loadStatuses function.The changes to the
loadStatuses
function implementation are well-structured:
- Creation of
scheduleStatuses
map improves data organization.- Proper use of
onData
andonError
callbacks enhances error handling and data flow.- The implementation aligns well with the updated function signature.
These changes contribute to better data management and error handling within the function.
131-135
: Enhanced SchedulesContext with improved type safety.The updates to
SchedulesContext
are well-considered:
- The context value type now includes
isLoading
anderror
states, aligning with the hook's return type.- Default values are provided for all properties, ensuring type safety when accessing the context.
These changes improve the consistency between the hook and the context, enhancing overall type safety.
Line range hint
137-149
: Improved SchedulesProvider with better type safety and consistency.The changes to
SchedulesProvider
are beneficial:
- Use of
PropsWithChildren
improves type safety for components with children.- The switch from
transform
prop toquery
prop aligns with the changes in theuseSchedules
hook.These updates enhance type safety and maintain consistency with the hook changes.
Line range hint
1-176
: Comprehensive improvements enhancing type safety, error handling, and query management.This file has undergone significant positive changes:
- Enhanced type safety with new type definitions and proper use of TypeScript features.
- Improved error handling throughout the
useSchedules
hook and related functions.- Better query management using
useRef
for proper cleanup and prevention of memory leaks.- Alignment with updates in other parts of the codebase, such as the use of
accountFilter
.- Improved flexibility in schedule querying with the new
accountSchedulesQuery
function.These changes collectively contribute to a more robust, maintainable, and type-safe implementation of schedule-related functionality.
Great job on these improvements! The code is now more resilient and easier to maintain.
packages/desktop-client/src/components/accounts/Reconcile.tsx (1)
Line range hint
1-180
: Summary: Good progress, but ensure consistency across components.Overall, the changes in this file align well with the PR objectives of migrating to functional components and using new hooks. The
ReconcilingMessage
component and its props have been successfully updated to use the new structure.Key points:
- The addition of
accountId
and simplification ofbalanceQuery
inReconcilingMessageProps
is a good improvement.- The
ReconcilingMessage
component correctly implements these changes.- The
ReconcileMenu
component needs to be updated to align with the newbalanceQuery
structure for consistency.Please address the suggested updates to
ReconcileMenu
to ensure full alignment with the new structure across the entire file.To ensure all necessary updates have been made, please run the following verification script:
packages/desktop-client/src/components/accounts/Balance.jsx (2)
117-124
: LGTM: Improved clarity and error prevention.The changes to
FilteredBalance
enhance both clarity and robustness:
- Renaming
filteredAmount
tofilteredBalance
improves readability.- Using
filteredBalance || 0
prevents potential issues with undefined values.These modifications align well with best practices for maintainable and error-resistant code.
128-137
: LGTM: Enhanced specificity in balance queries.The changes to
MoreBalances
improve the function's ability to handle multiple accounts:
- Adding
accountId
as a parameter allows for account-specific queries.- Including
accountId
inuseSheetValue
names prevents potential naming conflicts.These modifications enhance the component's flexibility and maintainability.
packages/loot-core/src/client/data-hooks/transactions.ts (2)
1-30
: LGTM: Imports and type definitions are well-structured.The imports and type definitions are appropriate for the functionality of the hooks. The types provide good type safety and clarity for the data structures used in the hooks.
1-232
: Summary: Well-implemented transaction hooks aligning with PR objectivesThis new file introduces well-structured and optimized hooks for transaction management, aligning perfectly with the PR's objective of simplifying transaction fetching across the codebase. The
useTransactions
,usePreviewTransactions
, anduseTransactionsSearch
hooks provide a solid foundation for managing transactions in functional components.The implementation demonstrates good use of React hooks, proper error handling, and performance optimizations. The minor issues and optimization suggestions provided in the review comments, if addressed, will further enhance the robustness and efficiency of these hooks.
Overall, this file significantly contributes to the migration from class-based components to functional components and the adoption of the new
useTransactions
hook, as outlined in the PR objectives.packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (4)
21-28
: Improved data management with new hooksThe introduction of
accountSchedulesQuery
and new transaction-related hooks (usePreviewTransactions
,useTransactions
,useTransactionsSearch
) indicates a positive shift towards more modular and reusable data fetching logic. This change likely improves separation of concerns and may enhance performance.
217-222
: Improved type safety for component propsThe changes to the
TransactionListWithPreviews
component props enhance type safety:
The
accountId
prop now includes specific string literals ('budgeted', 'offbudget', 'uncategorized') in addition to theAccountEntity['id']
type. This prevents potential errors by ensuring only valid account identifiers are passed.The
accountName
prop type now usesAccountEntity['name'] | string
, which allows for both account names from theAccountEntity
type and custom string names. This flexibility is useful while maintaining type safety.These improvements make the component's interface more precise and reduce the likelihood of type-related errors.
Line range hint
1-362
: Overall assessment: Significant improvements in component structure and functionalityThe changes made to the
AccountTransactions
component and its subcomponents represent a substantial improvement in several areas:
- Enhanced data management through the introduction of new hooks (
useTransactions
,useTransactionsSearch
, etc.).- Improved performance optimizations with proper use of
useCallback
anduseMemo
.- Better type safety with more precise prop types and union types.
- Refactored transaction querying and state management for more efficient data handling.
- Updated search functionality and transaction opening logic for a better user experience.
These changes collectively contribute to a more maintainable, performant, and type-safe component. The suggested minor improvements, if implemented, will further enhance the code quality.
Great job on this refactoring effort!
313-316
:⚠️ Potential issueFix typo and approve transaction display logic
The logic for combining preview and regular transactions based on the search state is correct. However, there's a typo in the variable name that needs to be fixed:
- const transcationsToDisplay = !isSearching + const transactionsToDisplay = !isSearching ? previewTransactions.concat(transactions) : transactions;This typo could lead to confusion and potential bugs, so it's important to correct it.
The logic itself is sound, displaying both preview and regular transactions when not searching, and only regular transactions during a search operation.
packages/desktop-client/src/components/accounts/Header.tsx (6)
11-11
: New Query type import addedThe addition of the
Query
type import from 'loot-core/shared/query' suggests a move towards more structured query handling. This is a positive change that should improve type safety and consistency in query-related operations.
22-22
: Unused import removedThe commented-out import of
AnimatedLoading
has been addressed as per the previous review comment. To keep the code clean, you can now safely remove this line entirely.
56-57
: AccountHeaderProps type updatedThe
AccountHeaderProps
type has been updated with several new properties:
isLoading
: booleanaccountId
: AccountEntity['id'] | stringtransactions
: readonly TransactionEntity[]balanceQuery
andfilteredQuery
: QueryshowFilteredBalance
andfilteredBalance
These changes reflect updates in the component's functionality and data handling. The use of
readonly
for transactions is a good practice for immutability.Also applies to: 65-65, 71-72, 75-76
133-134
: AccountHeader component updated with new props and local search stateThe
AccountHeader
component has been updated to include new props such asisLoading
,accountId
,balanceQuery
,showFilteredBalance
, andfilteredBalance
. These additions align with the changes made to theAccountHeaderProps
type.A new local state variable
search
has been added using theuseState
hook. This change suggests a shift towards managing search state locally within the component, which can improve performance and reduce unnecessary re-renders of parent components.Also applies to: 148-153, 199-199
361-376
: SelectedTransactionsButton component updated with new propsThe
SelectedTransactionsButton
component has been updated with several new props:
isLoading
: Reflects the loading state from the parent component.getTransaction
: Now uses the readonly transactions array, promoting immutability.- New props like
onDuplicate
,onLinkSchedule
,onUnlinkSchedule
,onSetTransfer
,onMakeAsSplitTransaction
, andonMakeAsNonSplitTransactions
have been added.These changes suggest enhanced functionality for transaction management, including support for split transactions and scheduling. The use of the readonly transactions array in the
getTransaction
prop is a good practice for maintaining data immutability.
296-300
: Balances component updated with new propsThe
Balances
component now receives additional props:
accountId
filteredBalance
showFilteredBalance
These new props suggest enhanced functionality for displaying account balances, potentially including filtered balance information. However, the
isLoading
prop has been commented out. Ensure that loading states are still being handled appropriately, either within theBalances
component or at a higher level in the component tree.packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (5)
8-8
: Excellent use of the newuseCategories
hook!The integration of the
useCategories
hook centralizes category management, promoting a single source of truth for categories. This change enhances maintainability and consistency across the application.Also applies to: 158-159
298-298
: Correct updates togetImportPreview
function.The changes in the category assignment logic and the addition of
categories
to the dependencies array are appropriate. This ensures that the function works correctly with the new category structure and re-creates when categories change.Also applies to: 313-313
587-587
: Appropriate update to category assignment inonImport
function.The change in category assignment logic aligns with the new category structure, ensuring consistency throughout the import process.
789-789
: Correct update tocategories
prop inTransaction
component.The change in the
categories
prop passed to theTransaction
component is consistent with the new category data structure, ensuring that child components receive the correct data.
Line range hint
1-1092
: Overall excellent refactoring to integrate theuseCategories
hook.The changes in this file consistently update the handling of categories throughout the
ImportTransactionsModal
component. By integrating theuseCategories
hook, the code now benefits from centralized category management, which improves maintainability and ensures a single source of truth for category data.Key improvements:
- Consistent updates to function signatures and logic to work with the new category structure.
- Proper integration of the
useCategories
hook in the component.- Updates to props passed to child components, ensuring data consistency.
These changes enhance the overall architecture of the application while maintaining existing functionality. Great job on this refactoring effort!
packages/desktop-client/src/components/table.tsx (2)
Line range hint
1-1293
: Summary of changesThe main modifications in this file are:
- Addition of loading state functionality to the
SelectedItemsButton
component, improving user feedback during asynchronous operations.- No significant changes observed in the
Table
component within the provided code snippet.Overall, the changes appear to enhance the user experience without introducing any major issues. The addition of the loading state is a positive improvement.
Line range hint
1-1293
: No significant changes observed in the Table component.The AI-generated summary mentioned updates to the
Table
component, but no substantial changes are visible in the provided code snippet. If there were intended changes to theTable
component, they might be in parts of the file not shown here.To confirm if there are any changes to the
Table
component that are not visible in the provided snippet, we can run the following script:This script will help us verify if there are any modifications to the
Table
component that we might have missed in the provided code snippet.packages/desktop-client/src/components/modals/EditRuleModal.jsx (1)
1-1
: LGTM: Import statements look good.The import statements are well-organized and include all necessary dependencies for the component.
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)
769-776
: Improved schedule handling in PayeeIcons componentThe changes to the
PayeeIcons
component enhance its functionality and performance:
- The
useCachedSchedules
hook is now used to manage the loading state and retrieve schedules.- The component now handles the loading state gracefully by returning null when
isLoading
is true.- The
schedule
is now derived from the cachedschedules
array, potentially reducing prop drilling and improving data consistency.These improvements should lead to better performance and a more robust handling of schedule-related data in the component.
Line range hint
1-2405
: Overall improvements in loading state and schedule data handlingThe changes made to this file, particularly in the
PayeeIcons
andTransactionTableInner
components, represent a positive step towards better handling of asynchronous data and loading states. Key improvements include:
- Use of
useCachedSchedules
hook inPayeeIcons
for more efficient schedule data management.- Graceful handling of loading states in
PayeeIcons
.- Addition of
isLoading
prop toTransactionTableInner
for propagating loading state to child components.These enhancements should result in:
- Improved performance due to better caching and state management.
- Enhanced user experience with more responsive loading indicators.
- More robust handling of asynchronous data throughout the transaction table.
While no major new features were introduced, these changes contribute to the overall stability and responsiveness of the component.
packages/desktop-client/src/hooks/usePreviewTransactions.ts (1)
27-66
: LGTM!The updates to the
usePreviewTransactions
hook enhance the efficiency and readability of the code. The use ofusePrevious
to track changes inscheduleData
and the added loading check improve the hook's performance.packages/desktop-client/src/components/schedules/SchedulesTable.tsx (5)
39-40
: Good addition ofisLoading
prop and enforcing immutabilityAdding the optional
isLoading
prop enhances the component's ability to manage loading states effectively. Changingschedules
toreadonly ScheduleEntity[]
enforces immutability, which is a best practice in React and TypeScript to prevent unintended mutations.
207-207
: Setting a default value forisLoading
propProviding a default value of
false
forisLoading
ensures that the component functions correctly even if the prop is not supplied, improving the robustness of the code.
278-279
: OptimizerenderSchedule
withuseCallback
Wrapping
renderSchedule
inuseCallback
improves performance by memoizing the function, preventing unnecessary re-creations on each render when dependencies haven't changed.
345-346
: ImproverenderItem
performance withuseCallback
Using
useCallback
forrenderItem
enhances performance by memoizing the function, ensuring it only changes when its dependencies do.
406-406
: PassisLoading
to theTable
component for proper loading indicationIncluding the
loading={isLoading}
prop ensures theTable
component properly reflects the loading state, enhancing the user experience during data fetch operations.
@@ -1,11 +1,12 @@ | |||
import React from 'react'; | |||
import React, { useMemo } from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Issue Found: Invalid import paths in ScheduleValue.tsx.
The import paths 'loot-core/client/data-hooks/schedules'
and 'loot-core/shared/query'
do not exist, causing module resolution errors.
🔗 Analysis chain
LGTM: Import changes are consistent with component updates.
The import changes align with the component's new requirements and the broader refactoring mentioned in the AI summary.
Let's verify the new import paths:
Also applies to: 3-4
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the existence and usage of the imported modules.
# Test 1: Check if 'useSchedules' is exported from the new path
rg -p "export.*useSchedules" "loot-core/client/data-hooks/schedules"
# Test 2: Check if 'q' is exported from the specified path
rg -p "export.*q" "loot-core/shared/query"
# Test 3: Verify consistent usage of 'useSchedules' across the codebase
rg "from 'loot-core/client/data-hooks/schedules'" -g '*.{ts,tsx,js,jsx}'
Length of output: 976
@@ -669,7 +674,7 @@ type AccountMenuProps = { | |||
account: AccountEntity; | |||
canSync: boolean; | |||
showBalances: boolean; | |||
canShowBalances: boolean; | |||
// canShowBalances: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Cleanup of commented-out canShowBalances references needed
The canShowBalances
property has been successfully removed from active code within the AccountMenu
component. However, several commented-out references to canShowBalances
still exist in packages/desktop-client/src/components/accounts/Header.tsx
. Please remove these commented lines to ensure the codebase remains clean and maintainable.
packages/desktop-client/src/components/accounts/Header.tsx:677
packages/desktop-client/src/components/accounts/Header.tsx:699
packages/desktop-client/src/components/accounts/Header.tsx:722-737
🔗 Analysis chain
AccountMenu component updated to always show balance toggle option
The AccountMenu
component has been modified to always include the option to toggle balance visibility:
- The
canShowBalances
prop has been removed. - The menu items for toggling balance visibility are now always included, regardless of any condition.
This change suggests that the ability to show/hide balances is now always available to users. Ensure that this aligns with the intended user experience and that there are no scenarios where hiding this option would be necessary.
Also applies to: 699-699, 722-737
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Verify if there are any remaining checks for canShowBalances in the codebase
rg "canShowBalances" --type ts --type tsx
Length of output: 223
Script:
#!/bin/bash
# Verify if there are any remaining checks for canShowBalances in the codebase
rg "canShowBalances" --type=typescript --type=typescriptreact
Length of output: 96
Script:
#!/bin/bash
# Verify if there are any remaining checks for canShowBalances in the codebase
rg "canShowBalances" -g "*.ts" -g "*.tsx"
Length of output: 422
/> | ||
{!minimal && ( | ||
<Field width={80} style={{ textAlign: 'center' }}> | ||
{schedule._date && schedule._date.frequency && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify conditional rendering using optional chaining
You can simplify the condition by using optional chaining, which makes the code cleaner and more concise.
Apply this diff to simplify the condition:
- {schedule._date && schedule._date.frequency && (
+ {schedule._date?.frequency && (
<SvgCheck style={{ width: 13, height: 13 }} />
)}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
{schedule._date && schedule._date.frequency && ( | |
{schedule._date?.frequency && ( |
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
959f0be
to
5ca076c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (16)
packages/loot-core/src/client/data-hooks/schedules.tsx (2)
26-50
: Enhanced error handling and data processing in loadStatuses function.The changes to the
loadStatuses
function improve error handling and data processing flexibility. The use of a Map forscheduleStatuses
is a good choice for performance.Consider using the nullish coalescing operator for the
onData
callback:- onData?.(scheduleStatuses); + onData?.(scheduleStatuses);This ensures that
onData
is called only if it's defined and not null or undefined.
154-176
: Well-structured and flexible accountSchedulesQuery function.The new
accountSchedulesQuery
function provides a flexible and reusable way to construct queries for account schedules. It handles different account types well and aligns with theaccountFilter
usage earlier in the file.Consider extracting the base query into a separate variable for improved readability:
const baseQuery = q('schedules') .select('*') .filter({ $and: [{ '_account.closed': false }], }); let query = baseQuery; // ... rest of the functionThis separation makes the base query more apparent and easier to modify if needed in the future.
packages/desktop-client/src/components/accounts/Balance.jsx (3)
45-45
: LGTM: SelectedBalance function improved with better parameter naming and loading state handling.The changes improve the function by using 'accountId' instead of 'account', which is a better practice for identifying accounts. The addition of loading state handling for schedules enhances the component's reliability.
However, consider initializing
scheduleBalance
to 0 instead of null:-let scheduleBalance = null; +let scheduleBalance = 0;This would align with the calculation logic and prevent potential NaN issues.
Also applies to: 71-77
149-152
: LGTM: Balances function parameters updated for improved functionality.The changes to the function parameters align well with the overall improvements in the component. The use of 'accountId' and 'balanceQuery' instead of 'account' provides more flexibility, and the addition of 'filteredBalance' and 'showFilteredBalance' parameters suggests new filtering capabilities.
Consider adding JSDoc comments to document these new parameters and their purpose.
Line range hint
176-226
: LGTM: Improved button component with loading state and updated styling.The changes enhance the user experience by introducing a loading state and improve code maintainability with scoped styling using @emotion/css. The updates to child components maintain consistency with the overall accountId changes.
Consider extracting the styles into a separate constant or file for better organization:
const buttonStyles = css({ paddingTop: 1, paddingBottom: 1, // ... other styles }); // Then use it like this: className={buttonStyles}This would improve readability and make the styles more reusable.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
97-180
: Approve implementation with a minor optimization suggestion.The
usePreviewTransactions
hook is well-implemented, effectively managing preview transactions based on schedule data. The use ofuseMemo
forscheduleTransactions
is a good optimization to prevent unnecessary recalculations.The effect that applies rules to the preview transactions is correctly implemented, including proper handling of unmounting and error states. The returned object provides all necessary data along with loading and error states.
Consider optimizing the effect dependency array on line 173:
- }, [scheduleTransactions, schedules, statuses]); + }, [scheduleTransactions]);
schedules
andstatuses
are already dependencies ofscheduleTransactions
, so including them in the effect's dependency array is redundant. This change might prevent unnecessary effect runs.packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (2)
Line range hint
106-155
: Optimized callback functions with useCallbackThe refactoring of callback functions using
useCallback
is a positive change that can help optimize performance, especially in larger components. The explicit dependency lists ensure that these callbacks are only recreated when necessary.These changes align well with React best practices for optimizing functional components.
Consider wrapping the
onSaveNotes
function withuseCallback
as well for consistency:- const onSaveNotes = useCallback(async (id: string, notes: string) => { + const onSaveNotes = useCallback( + async (id: string, notes: string) => { await send('notes-save', { id, note: notes }); - }, []); + }, + [] + );
224-240
: Improved transaction management with custom hooksThe refactoring of the
TransactionListWithPreviews
component to use custom hooks (useTransactions
anduseTransactionsSearch
) is a significant improvement. This change simplifies the component logic, improves separation of concerns, and should make the code more maintainable.The introduction of
baseTransactionsQuery
ensures consistent query creation, which is a good practice.These changes align well with React best practices and should improve the overall structure and performance of the component.
Consider renaming the
transcationsToDisplay
variable totransactionsToDisplay
to fix the typo:- const transcationsToDisplay = !isSearching + const transactionsToDisplay = !isSearching ? previewTransactions.concat(transactions) : transactions;Also applies to: 242-243, 277-281
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)
278-343
: Improved performance and readability with useCallbackThe refactoring of
renderSchedule
into a memoized function usinguseCallback
is a great improvement:
- It can prevent unnecessary re-renders, potentially improving performance.
- The function's logic is more concise and readable.
- Style properties have been updated for better consistency.
One minor suggestion:
Consider destructuring
schedule
in the function parameters for cleaner code:- const renderSchedule = useCallback( - ({ schedule }: { schedule: ScheduleEntity }) => { + const renderSchedule = useCallback( + ({ schedule }: { schedule: ScheduleEntity }) => {Overall, these changes enhance both performance and code quality. Well done!
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
Potential Type Safety Issue with
items
CastingThe
items
prop inSchedulesTable.tsx
is being cast toScheduleEntity[]
, which may hide potential type errors. Consider specifying the generic type for theTable
component to ensure type safety without relying on type casting.
- File:
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
- Lines: 403-409
🔗 Analysis chain
Line range hint
406-412
: Improved loading state handling and rendering logicThe changes to the main component return statement are good:
- Adding the
loading
prop to theTable
component improves user experience during data fetching.- Using the memoized
renderItem
function for rendering is consistent with earlier changes.However, there's a potential area for improvement:
The type casting of
items
toScheduleEntity[]
might hide potential type errors. Consider refactoring theTable
component or theitems
type to avoid this cast. Here's a script to verify the usage ofScheduleEntity
type in the codebase:This will help ensure type safety throughout the component. Otherwise, the changes look good and improve the overall functionality of the component.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check usage of ScheduleEntity type and Table component props # Test: Search for ScheduleEntity type usage echo "ScheduleEntity type usage:" rg "ScheduleEntity(\[])?" --type typescript # Test: Search for Table component props echo "\nTable component props:" rg "Table\s*<.*?>" --type typescriptLength of output: 276
Script:
#!/bin/bash # Description: Check usage of ScheduleEntity type and Table component props in TypeScript files # Test: Search for ScheduleEntity type usage in .ts and .tsx files echo "ScheduleEntity type usage:" rg "ScheduleEntity(\[])?" -g "*.ts" -g "*.tsx" # Test: Search for Table component props in .ts and .tsx files echo "\nTable component props:" rg "Table\s*<.*?>" -g "*.ts" -g "*.tsx"Length of output: 5582
packages/desktop-client/src/components/accounts/Header.tsx (1)
22-22
: Unused import removedThe import for
AnimatedLoading
has been commented out. If this component is no longer used in the file, it's good practice to remove the import entirely rather than commenting it out.Consider removing the commented-out import entirely:
-// import { AnimatedLoading } from '../../icons/AnimatedLoading';
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (4)
Line range hint
243-253
: Simplified parseCategoryFields functionThe changes to the
parseCategoryFields
function align well with the new category management approach. The function is now more straightforward and flexible.Consider using
Array.find
for a more concise implementation:function parseCategoryFields(trans, categories) { const match = categories.find(category => category.name === trans.category); return match ? match.id : null; }This suggestion maintains the current functionality while potentially improving readability and performance.
Line range hint
298-302
: Updated getImportPreview function for new category structureThe changes in the
getImportPreview
function correctly adapt to the new category management approach. The inclusion ofcategories
in the dependency array ofuseCallback
is crucial for maintaining consistency.Consider memoizing the categories array to potentially optimize performance:
const memoizedCategories = useMemo(() => categories, [categories]);Then use
memoizedCategories
in place ofcategories
throughout the component. This could help prevent unnecessary re-renders if the categories array reference changes but its content remains the same.Also applies to: 313-313
789-792
: Updated Transaction component prop for new category structureThe changes to the Transaction component prop correctly pass the new category structure. This ensures consistency in how categories are handled throughout the component tree.
Consider memoizing the Transaction component or its props to potentially optimize performance, especially if this component is rendered frequently:
const memoizedTransaction = useMemo( () => ( <Transaction transaction={item} // ... other props ... categories={categories} onCheckTransaction={onCheckTransaction} reconcile={reconcile} /> ), [item, categories, onCheckTransaction, reconcile] );Then use
memoizedTransaction
in place of the inline Transaction component. This could help prevent unnecessary re-renders if the props remain the same.
Line range hint
1-1048
: Overall assessment: Improved category management and component structureThe changes made to the
ImportTransactionsModal
component significantly improve its structure and data management, particularly in handling categories. The introduction of theuseCategories
hook and consistent updates throughout the component enhance data consistency and potentially improve performance.Key improvements:
- Centralized category management with
useCategories
hook- Simplified and more flexible
parseCategoryFields
function- Consistent updates in
getImportPreview
,onImport
, and the render methodThese changes align well with React best practices and are likely to improve the overall maintainability of the component. The suggestions for further optimization (like memoization) could be considered in future iterations to potentially enhance performance, especially if this component handles large datasets or is rendered frequently.
packages/desktop-client/src/components/table.tsx (1)
819-819
: Consider makingisLoading
optional inSelectedItemsButtonProps
The
isLoading
prop has been added as a required property in theSelectedItemsButtonProps
interface. However, since it has a default value in the component implementation, it would be more flexible to make it optional in the type definition.Consider updating the type definition as follows:
type SelectedItemsButtonProps<Name extends string> = { id: string; name: ((count: number) => string) | string; items: MenuItem<Name>[]; onSelect: (name: Name, items: string[]) => void; - isLoading: boolean; + isLoading?: boolean; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (22)
- packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
- packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
- packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
- packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
- packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
- packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
- packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
- packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
- packages/desktop-client/src/components/table.tsx (4 hunks)
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
- packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
- packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
- packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
- packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- packages/desktop-client/src/components/ManageRules.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/modals/EditRuleModal.jsx
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
- packages/desktop-client/src/components/rules/ScheduleValue.tsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/schedules/index.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (2)
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-10-08T15:46:15.739Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-09-27T14:15:46.637Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (32)
packages/loot-core/src/client/data-hooks/schedules.tsx (3)
7-8
: Improved type safety and alignment with other file changes.The addition of new imports and type definitions enhances type safety and aligns with changes in other parts of the codebase. The switch from
getAccountFilter
toaccountFilter
maintains consistency with updates in thequeries.ts
file.Also applies to: 15-20
69-126
: Improved state management and effect cleanup in useSchedules hook.The changes to the
useSchedules
hook significantly improve state management and align with React best practices. The use ofuseRef
for query references and enhanced effect cleanup prevent potential memory leaks.Regarding the past review comment about potential state updates after unmount, the current implementation already addresses this concern. The
isUnmounted
flag is used to prevent state updates after the component unmounts:if (!isUnmounted) { setIsLoading(false); setData({ schedules, statuses }); }This implementation effectively prevents state updates after the component unmounts, resolving the issue raised in the previous review.
Line range hint
131-149
: Improved flexibility and consistency in SchedulesProvider and SchedulesContext.The changes to
SchedulesProvider
andSchedulesContext
improve flexibility in query construction and ensure consistency with theuseSchedules
hook's return value. These updates align well with the overall refactoring of the component.packages/desktop-client/src/components/accounts/Balance.jsx (5)
1-1
: LGTM: Import statements cleaned up and updated.The changes in the import statements improve code cleanliness by removing unused imports and adding necessary ones for the updated component implementation. This addresses the previous review comments about unused imports.
Also applies to: 4-4, 14-14
90-93
: LGTM: Improved schedule balance calculation logic.The updated condition for calculating
scheduleBalance
now correctly usesaccountId
for comparison, which is more precise and consistent with the overall changes in the component.
117-117
: LGTM: FilteredBalance function improved with better naming and default value handling.The changes enhance the function by using a more descriptive parameter name 'filteredBalance' and ensuring a valid number is always passed to DetailedBalance with 'filteredBalance || 0'.
Also applies to: 122-122
128-128
: LGTM: MoreBalances function updated to use accountId for more specific querying.The changes improve the function by incorporating 'accountId' in both the function parameters and the useSheetValue hook names. This allows for more precise querying and better cache management.
Also applies to: 132-133, 136-137
158-165
: LGTM: Improved performance with memoized balanceBinding.The introduction of useMemo for balanceBinding is a good optimization. It prevents unnecessary recalculations when other props change, potentially improving the component's performance.
packages/loot-core/src/client/data-hooks/transactions.ts (2)
194-222
: Approve implementation of useTransactionsSearch hook.The
useTransactionsSearch
hook is well-implemented and provides an efficient way to handle transaction searches.Key points:
- The use of
useMemo
withdebounce
is an excellent optimization, preventing unnecessary function recreations and providing a smooth user experience.- The search function correctly handles empty input by resetting the query and updating the searching state.
- The hook properly updates the query using the provided
updateQuery
function when a valid search text is entered.- The returned object provides the necessary
search
function andisSearching
state, allowing components to easily integrate search functionality.The implementation is clean, efficient, and follows React best practices.
224-230
: Approve implementation of isForPreview utility function.The
isForPreview
function is concise and correctly implements the logic for determining whether a schedule should be included in the preview.The function checks for two conditions:
- The schedule is not completed (
!schedule.completed
).- The schedule status is either 'due', 'upcoming', or 'missed'.
This implementation effectively filters the schedules for preview purposes, ensuring that only relevant schedules are included.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
21-28
: Improved data management and type safetyThe changes in imports and component structure show a positive shift towards using custom hooks for managing schedules and transactions. The use of
accountSchedulesQuery
and the updatedSchedulesProvider
props suggest a more efficient and type-safe approach to data fetching. The expanded type definition foraccountId
in theAccountTransactions
component props enhances type safety and flexibility.These changes align well with modern React practices and should improve the overall maintainability of the component.
Also applies to: 59-62, 81-86
283-305
: Improved transaction handling logicThe refactored
onOpenTransaction
function now efficiently handles both regular and preview transactions. The use ofisPreviewId
to differentiate between transaction types is a clean approach. For preview transactions, dispatching a modal for scheduled transaction actions provides a good user experience.This unified approach to handling different transaction types improves code organization and maintainability.
313-324
: Improved TransactionListWithBalances usageThe updates to the
TransactionListWithBalances
component usage show good improvements in handling loading states and transaction data. Combining the loading states for both regular and preview transactions provides a more accurate representation of the overall loading status.These changes simplify the component's props and improve the overall user experience by providing more accurate loading indicators.
As mentioned earlier, there's a typo in the variable name
transcationsToDisplay
. It should betransactionsToDisplay
.packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4)
2-8
: Improved type safety and performance optimization preparationThe changes to imports and type definitions are well-considered:
- Adding
useCallback
prepares for performance optimizations.- Including
CSSProperties
improves type checking for style objects.- The new
isLoading
prop inSchedulesTableProps
allows for better loading state management.- Making
schedules
readonly inSchedulesTableProps
enforces immutability, which is a good practice for React props.These updates enhance type safety and set the stage for better performance. Good job!
Also applies to: 39-40
Line range hint
261-276
: Consistent use of readonly for immutabilityThe
items
variable is now correctly typed asreadonly SchedulesTableItem[]
, which is consistent with the earlier change to theschedules
prop type. This enforces immutability throughout the component, which is a good practice in React for preventing accidental mutations.The logic for filtering and handling completed schedules remains intact, ensuring that the functionality is preserved while improving type safety.
345-375
: Consistent use of useCallback and clear item rendering logicThe new
renderItem
function is well-implemented:
- It uses
useCallback
for memoization, consistent with therenderSchedule
function.- The logic for handling different item types (completed schedules vs. regular items) is clear and concise.
- The function effectively delegates to
renderSchedule
for regular schedule items.This implementation maintains consistency in the codebase and provides a clear separation of concerns for rendering different item types. Good job!
Line range hint
1-415
: Overall assessment: Excellent improvements to SchedulesTable componentThe changes made to the SchedulesTable component are commendable:
- Enhanced type safety with readonly types and better prop definitions.
- Improved performance through the use of
useCallback
anduseMemo
.- Better code organization and readability with the refactored
renderSchedule
andrenderItem
functions.- Improved user experience with the addition of loading state handling.
These changes align well with modern React practices and TypeScript best practices. The component is now more maintainable, performant, and type-safe. Great job on this refactoring!
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/desktop-client/src/components/accounts/Header.tsx (9)
11-11
: New import added for Query typeThe
Query
type is now imported from 'loot-core/shared/query'. This suggests that the component is now using a more specific query type, which could improve type safety and consistency across the application.
56-57
: Updates to AccountHeaderProps typeSeveral changes have been made to the
AccountHeaderProps
type:
isLoading
property addedaccountId
property addedtransactions
changed to readonly arraybalanceQuery
andfilteredQuery
properties updated/addedshowFilteredBalance
andfilteredBalance
properties addedThese changes suggest improvements in type safety and the addition of new functionality related to filtered balances and loading states.
Also applies to: 65-65, 71-72, 75-76
133-134
: Updates to AccountHeader component propsThe
AccountHeader
component now accepts new props such asisLoading
,accountId
,showFilteredBalance
, andfilteredBalance
. Some props liketransactionsQuery
andcanCalculateBalance
have been commented out.These changes align with the updates made to the
AccountHeaderProps
type and suggest a shift in how data is being passed and managed within the component.Also applies to: 148-153, 155-155
199-199
: Search state management updatedThe component now manages its own
search
state usinguseState
hook. TheonChange
handler for theSearch
component has been updated to set both the local state and call theonSearch
prop.This change allows for more flexible search functionality, potentially enabling immediate local filtering while still allowing parent components to react to search changes.
Also applies to: 355-358
361-376
: Updates to SelectedTransactionsButton componentThe
SelectedTransactionsButton
component now receives several new props, includingisLoading
and various callback functions. ThegetTransaction
prop now uses the readonlytransactions
array.These changes improve the component's functionality by adding loading state handling and providing more granular control over various actions related to selected transactions.
378-405
: New Reconcile button and menu addedA new Reconcile button and associated popover menu have been added for account reconciliation. This feature allows users to initiate the reconciliation process for the account.
The implementation looks good, using the
Popover
component for the menu and managing its open state withuseState
.
513-513
: Update to ReconcilingMessage component propsThe
ReconcilingMessage
component now receivesaccountId
as a prop. This change aligns with the addition ofaccountId
to theAccountHeaderProps
and suggests that the reconciliation process now requires the account ID.
296-300
: Updates to Balances component propsThe
Balances
component now receivesaccountId
,filteredBalance
, andshowFilteredBalance
as props. TheisLoading
prop has been commented out.These changes suggest that the
Balances
component has been updated to handle filtered balance display, which aligns with the new properties added toAccountHeaderProps
.To ensure these prop changes are consistent with the
Balances
component implementation, let's verify its definition:✅ Verification successful
Verification Complete: Balances Component Props Updated Correctly
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Verify Balances component props rg "export function Balances\(" packages/desktop-client/src/components/accounts/Balance.tsx -A 10Length of output: 199
Script:
#!/bin/bash # Search for Balances.tsx in the components directory fd "Balances.tsx" packages/desktop-client/src/components/ # If not found, search for the Balances component export ast-grep --pattern $'export function Balances($_) { $$$ }' packages/desktop-client/src/components/Length of output: 7861
677-677
: Removal of canShowBalances functionalityThe
canShowBalances
prop and related functionality have been commented out in theAccountMenu
component. The menu item for toggling balance visibility is now always included, regardless of any condition.This change suggests that the ability to show/hide balances is now always available to users. Ensure that this aligns with the intended user experience and that there are no scenarios where hiding this option would be necessary.
Let's verify if there are any remaining checks for
canShowBalances
in the codebase:Also applies to: 699-699, 722-737
✅ Verification successful
Verification Successful
No active checks for
canShowBalances
found in the codebase. All instances are commented out inpackages/desktop-client/src/components/accounts/Header.tsx
.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Verify if there are any remaining checks for canShowBalances in the codebase rg "canShowBalances" --type ts --type tsxLength of output: 69
Script:
#!/bin/bash # Re-verify if there are any remaining checks for canShowBalances in .ts and .tsx files rg "canShowBalances" -g "*.ts" -g "*.tsx"Length of output: 422
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (2)
8-8
: Improved category management with useCategories hookThe addition of the
useCategories
hook and its usage in the component is a positive change. This centralized approach to category management can lead to better data consistency and reusability across the application.Also applies to: 158-159
587-588
: Updated onImport function for new category structureThe changes in the
onImport
function correctly adapt to the new category management approach. This ensures consistency in how categories are handled during the import process.packages/desktop-client/src/components/table.tsx (1)
827-827
: LGTM:isLoading
prop implementation inSelectedItemsButton
The
isLoading
prop has been correctly added to theSelectedItemsButton
component with a default value offalse
. TheButtonWithLoading
component is now used instead of a regular button, which is a good practice for showing loading states.Also applies to: 842-856
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)
769-776
: Efficient handling of loading state inPayeeIcons
The added check for
isLoading
ensures thatPayeeIcons
does not attempt to accessschedules
before they are loaded, preventing potential runtime errors due to accessing undefined data. This change enhances the reliability of the component during data fetching.
Line range hint
1765-1775
: Properly passingisLoading
prop toTransactionTableInner
Adding
isLoading
as a prop toTransactionTableInner
allows the component to manage and reflect the loading state of transactions effectively. This change facilitates better user experience by enabling loading indicators or placeholders when data is being fetched.
1994-1994
: PassingisLoading
to theTable
component to display loading stateBy passing
loading={isLoading}
to theTable
component, the table now correctly reflects the loading state of the data. This ensures that the user interface provides appropriate feedback during data fetch operations, enhancing usability.
export function useTransactions({ | ||
query, | ||
options = { pageCount: 50 }, | ||
}: UseTransactionsProps): UseTransactionsResult { | ||
const [isLoading, setIsLoading] = useState(false); | ||
const [error, setError] = useState<Error | undefined>(undefined); | ||
const [transactions, setTransactions] = useState< | ||
ReadonlyArray<TransactionEntity> | ||
>([]); | ||
|
||
const pagedQueryRef = useRef<PagedQuery<TransactionEntity> | null>(null); | ||
|
||
// We don't want to re-render if options changes. | ||
// Putting options in a ref will prevent that and | ||
// allow us to use the latest options on next render. | ||
const optionsRef = useRef(options); | ||
optionsRef.current = options; | ||
|
||
useEffect(() => { | ||
let isUnmounted = false; | ||
|
||
setError(undefined); | ||
setIsLoading(query !== null); | ||
|
||
if (!query) { | ||
return; | ||
} | ||
|
||
if (query.state.table !== 'transactions') { | ||
setError(new Error('Query must be a transactions query.')); | ||
return; | ||
} | ||
|
||
pagedQueryRef.current = pagedQuery<TransactionEntity>(query, { | ||
onData: data => { | ||
if (!isUnmounted) { | ||
setTransactions(data); | ||
setIsLoading(false); | ||
} | ||
}, | ||
onError: setError, | ||
options: { pageCount: optionsRef.current.pageCount }, | ||
}); | ||
|
||
return () => { | ||
isUnmounted = true; | ||
pagedQueryRef.current?.unsubscribe(); | ||
}; | ||
}, [query]); | ||
|
||
return { | ||
transactions, | ||
isLoading, | ||
error, | ||
reload: pagedQueryRef.current?.run, | ||
loadMore: pagedQueryRef.current?.fetchNext, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approve implementation with a minor suggestion for loading state.
The useTransactions
hook implementation is well-structured and optimized. The use of refs for options and paged query is a good approach to prevent unnecessary re-renders. The error handling and cleanup in the effect are correctly implemented.
However, there's a minor issue with how the loading state is set:
On line 54, the condition query !== null
doesn't account for undefined
. Consider using !!query
instead:
- setIsLoading(query !== null);
+ setIsLoading(!!query);
This change ensures that isLoading
is set correctly for both null
and undefined
query values.
The rest of the implementation, including the paged query setup, error handling, and the returned object, is well-implemented and provides the necessary functionality for managing transactions data.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export function useTransactions({ | |
query, | |
options = { pageCount: 50 }, | |
}: UseTransactionsProps): UseTransactionsResult { | |
const [isLoading, setIsLoading] = useState(false); | |
const [error, setError] = useState<Error | undefined>(undefined); | |
const [transactions, setTransactions] = useState< | |
ReadonlyArray<TransactionEntity> | |
>([]); | |
const pagedQueryRef = useRef<PagedQuery<TransactionEntity> | null>(null); | |
// We don't want to re-render if options changes. | |
// Putting options in a ref will prevent that and | |
// allow us to use the latest options on next render. | |
const optionsRef = useRef(options); | |
optionsRef.current = options; | |
useEffect(() => { | |
let isUnmounted = false; | |
setError(undefined); | |
setIsLoading(query !== null); | |
if (!query) { | |
return; | |
} | |
if (query.state.table !== 'transactions') { | |
setError(new Error('Query must be a transactions query.')); | |
return; | |
} | |
pagedQueryRef.current = pagedQuery<TransactionEntity>(query, { | |
onData: data => { | |
if (!isUnmounted) { | |
setTransactions(data); | |
setIsLoading(false); | |
} | |
}, | |
onError: setError, | |
options: { pageCount: optionsRef.current.pageCount }, | |
}); | |
return () => { | |
isUnmounted = true; | |
pagedQueryRef.current?.unsubscribe(); | |
}; | |
}, [query]); | |
return { | |
transactions, | |
isLoading, | |
error, | |
reload: pagedQueryRef.current?.run, | |
loadMore: pagedQueryRef.current?.fetchNext, | |
}; | |
} | |
export function useTransactions({ | |
query, | |
options = { pageCount: 50 }, | |
}: UseTransactionsProps): UseTransactionsResult { | |
const [isLoading, setIsLoading] = useState(false); | |
const [error, setError] = useState<Error | undefined>(undefined); | |
const [transactions, setTransactions] = useState< | |
ReadonlyArray<TransactionEntity> | |
>([]); | |
const pagedQueryRef = useRef<PagedQuery<TransactionEntity> | null>(null); | |
// We don't want to re-render if options changes. | |
// Putting options in a ref will prevent that and | |
// allow us to use the latest options on next render. | |
const optionsRef = useRef(options); | |
optionsRef.current = options; | |
useEffect(() => { | |
let isUnmounted = false; | |
setError(undefined); | |
setIsLoading(!!query); | |
if (!query) { | |
return; | |
} | |
if (query.state.table !== 'transactions') { | |
setError(new Error('Query must be a transactions query.')); | |
return; | |
} | |
pagedQueryRef.current = pagedQuery<TransactionEntity>(query, { | |
onData: data => { | |
if (!isUnmounted) { | |
setTransactions(data); | |
setIsLoading(false); | |
} | |
}, | |
onError: setError, | |
options: { pageCount: optionsRef.current.pageCount }, | |
}); | |
return () => { | |
isUnmounted = true; | |
pagedQueryRef.current?.unsubscribe(); | |
}; | |
}, [query]); | |
return { | |
transactions, | |
isLoading, | |
error, | |
reload: pagedQueryRef.current?.run, | |
loadMore: pagedQueryRef.current?.fetchNext, | |
}; | |
} |
1e4a3a4
to
a512027
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (12)
packages/loot-core/src/client/data-hooks/filters.ts (1)
23-25
: LGTM: Migrated touseQuery
hookThe migration from
useLiveQuery
touseQuery
is consistent with the PR objectives and improves data fetching across the codebase.However, consider adding a comment explaining why an empty array is used as the second argument to
useQuery
. This would clarify that the query has no dependencies and will only run once when the component mounts.packages/loot-core/src/client/data-hooks/schedules.tsx (3)
26-50
: Enhanced error handling and data processingThe updates to
loadStatuses
function improve error handling and data processing flexibility. The use ofreadonly
type andMap
aligns well with immutability practices.Consider wrapping the
onData
callback in a try-catch block to handle any potential errors during the status calculation:onData: data => { try { // ... existing code ... onData?.(scheduleStatuses); } catch (error) { onError(error instanceof Error ? error : new Error(String(error))); } },This ensures that any unexpected errors during status calculation are properly caught and propagated.
54-78
: Improved type definitions and state managementThe new type definitions and changes to the
useSchedules
hook significantly enhance type safety, readability, and state management. The use ofuseRef
for live queries is a good practice for managing subscriptions across renders.Consider using a discriminated union type for
UseSchedulesResult
to make the loading and error states more explicit:type UseSchedulesResult = | { readonly isLoading: true; readonly error?: undefined; schedules: []; statuses: Map<string, never> } | { readonly isLoading: false; readonly error: Error; schedules: []; statuses: Map<string, never> } | { readonly isLoading: false; readonly error?: undefined } & ScheduleData;This approach can provide more precise type information based on the current state of the hook.
151-173
: Well-structured query builder functionThe new
accountSchedulesQuery
function is a valuable addition, providing a centralized and flexible way to construct queries for account schedules. The use ofaccountFilter
and the special handling for 'uncategorized' accounts demonstrate thoughtful query construction.To further enhance type safety, consider using a union type for the
accountId
parameter:type AccountIdParam = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized'; export function accountSchedulesQuery(accountId?: AccountIdParam) { // ... existing code ... }This change would make the allowed values for
accountId
more explicit and catch potential typos at compile-time.packages/desktop-client/src/components/accounts/Balance.jsx (5)
45-45
: LGTM: SelectedBalance function improved with better parameter naming and loading state handling.The changes in the
SelectedBalance
function look good. The parameter change from 'account' to 'accountId' improves consistency. The addition of loading state handling for schedules enhances the component's robustness.Consider renaming the destructured variable
isLoading
to something more specific, likeisSchedulesLoading
, to improve clarity:- const { isLoading, schedules = [] } = useCachedSchedules(); + const { isLoading: isSchedulesLoading, schedules = [] } = useCachedSchedules(); - if (isLoading) { + if (isSchedulesLoading) { return null; }Also applies to: 72-76
149-152
: LGTM: Balances function signature updated with new parameters.The
Balances
function signature has been appropriately updated with new parameters that reflect changes in how balance data is passed and managed. The addition ofshowFilteredBalance
suggests new functionality for displaying filtered balances.Consider adding JSDoc comments to document the purpose and expected types of the new parameters, especially
balanceQuery
andshowFilteredBalance
. This will improve code maintainability and help other developers understand the component's API.
157-165
: LGTM: balanceBinding logic improved with useMemo.The changes to the
balanceBinding
logic look good. The use ofuseMemo
can improve performance by memoizing the value, and the dependencies are correctly set toaccountId
andbalanceQuery
.Consider adjusting the formatting of the
useMemo
hook for better readability:const balanceBinding = useMemo( () => ({ name: `balance-query-${accountId}`, query: balanceQuery, value: 0, }), [accountId, balanceQuery] );
Line range hint
176-216
: LGTM: ButtonWithLoading component updated with improved styling and loading state handling.The changes to the button component look good. The switch to
ButtonWithLoading
and the addition of theisLoading
prop improve the handling of loading states. The use of@emotion/css
for styling is consistent with modern practices and the import changes.Consider extracting the button styles into a separate constant or styled component to improve code organization and reusability. For example:
const buttonStyles = css` padding-top: 1px; padding-bottom: 1px; & svg { width: 10px; height: 10px; margin-left: 10px; color: ${theme.pillText}; transform: ${props => props.showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)'}; opacity: ${props => props.selectedItemsSize > 0 || props.showExtraBalances ? 1 : 0}; } &[data-hovered] svg { opacity: 1; } `; // Then use it in the component: <ButtonWithLoading // ... other props className={buttonStyles} > {/* ... button content */} </ButtonWithLoading>
217-223
: LGTM: MoreBalances component usage updated correctly.The changes to the
MoreBalances
component usage look good. The addition of theaccountId
prop and thebalanceQuery
prop align with the updates to theMoreBalances
function signature.Remove the commented-out
transactionsQuery
prop to keep the code clean:<MoreBalances accountId={accountId} - // transactionsQuery={transactionsQuery} balanceQuery={balanceQuery} />
packages/loot-core/src/client/data-hooks/transactions.ts (1)
95-186
: LGTM: Well-implementedusePreviewTransactions
hook with room for optimizationThe
usePreviewTransactions
hook is well-implemented with proper error handling, loading state management, and effect cleanup. The use ofuseMemo
forscheduleTransactions
is a good optimization.Consider memoizing the
Promise.all
callback to prevent unnecessary recreations on each render:const applyRulesToTransactions = useCallback((transactions) => { return Promise.all( transactions.map(transaction => send('rules-run', { transaction }) ) ); }, []); // Use applyRulesToTransactions in the effect applyRulesToTransactions(scheduleTransactions) .then(newTrans => { // ... existing logic })This optimization could potentially improve performance, especially if the component re-renders frequently.
packages/desktop-client/src/components/transactions/TransactionList.jsx (1)
Line range hint
107-119
: LGTM: Improved handling of date changes inonSave
The modifications to the
onSave
function improve the handling of date changes in transactions. By updating thesort_order
and triggering a refetch when the date changes, you ensure that the transaction list remains properly sorted and up-to-date.Consider adding a comment explaining why
Date.now()
is used forsort_order
, as it might not be immediately obvious to other developers.packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (1)
285-307
: Dependencies inonOpenTransaction
callbackIn the
onOpenTransaction
callback, you might want to includeisPreviewId
in the dependency array ofuseCallback
. Although it's a utility function, including it ensures that all dependencies are accounted for, especially ifisPreviewId
ever changes.Suggestion:
}, [ dispatch, navigate, + isPreviewId, ]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (24)
- packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
- packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
- packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
- packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
- packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
- packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
- packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
- packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
- packages/desktop-client/src/components/table.tsx (4 hunks)
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
- packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
- packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
- packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
- packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
- packages/loot-core/src/client/query-helpers.ts (4 hunks)
- packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
- packages/desktop-client/src/components/ManageRules.tsx
- packages/desktop-client/src/components/accounts/Header.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/modals/EditRuleModal.jsx
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
- packages/desktop-client/src/components/rules/ScheduleValue.tsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/schedules/index.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (3)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (2)
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-10-08T15:46:15.739Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-09-27T14:15:46.637Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
packages/loot-core/src/client/data-hooks/filters.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88 Timestamp: 2024-10-22T02:08:48.162Z Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88 Timestamp: 2024-10-22T02:08:48.162Z Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/loot-core/src/client/query-helpers.ts
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
🔇 Additional comments (48)
packages/loot-core/src/client/data-hooks/filters.ts (4)
2-2
: LGTM: Import changes align with new hook usageThe import changes are consistent with the migration to functional components and the use of the new
useQuery
hook. This aligns well with the PR objectives of simplifying transaction fetching across the codebase.Also applies to: 6-6
8-8
: LGTM: Improved type safety with return type annotationThe addition of the
TransactionFilterEntity[]
return type to thetoJS
function enhances type safety and improves code readability. This is a good TypeScript practice.
29-35
: LGTM: Efficient sorting implementation withuseCallback
The new
sort
function implementation usinguseCallback
is a good approach for memoization. The sorting logic is correct and handles edge cases well.Regarding the previous comment about using
toSorted
, the current implementation with[...filters].sort()
is actually preferable. It creates a new array before sorting, which is a good practice to avoid mutating the original array, and it has broader browser compatibility thantoSorted
.
37-37
: LGTM: Efficient memoization of sorted filtersThe use of
useMemo
with the correct dependencies (data
andsort
) ensures that the filters are only re-sorted when necessary. This implementation aligns well with the learning from PR #3685 about avoiding unnecessary rerenders when options change.The spread operator
[...data]
is used correctly to create a new array, maintaining immutability.packages/loot-core/src/client/data-hooks/schedules.tsx (4)
7-20
: Improved type safety and module organizationThe addition of new imports, especially the explicit types from the models, enhances type safety throughout the file. The change in the
accountFilter
import location suggests a positive refactoring of the queries module, improving code organization.
80-117
: Robust error handling and resource managementThe changes in the
useEffect
hook significantly improve error handling, query validation, and resource management. TheisUnmounted
flag effectively addresses the concern raised in the previous review about preventing state updates after component unmount.The cleanup function now properly unsubscribes from both queries, preventing potential memory leaks. The query validation ensures that the hook is used correctly with schedules queries only.
These improvements align well with React best practices and enhance the overall reliability of the component.
128-140
: Improved context and provider implementationThe updates to
SchedulesContext
andSchedulesProvider
align well with the changes made to theuseSchedules
hook. IncludingisLoading
anderror
states in the context provides consumers with more comprehensive information about the current state of the schedules data.The change from a
transform
prop to aquery
prop inSchedulesProvider
offers more flexibility in how queries are constructed and passed down the component tree. This change is consistent with the broader refactoring efforts seen in the file.
Line range hint
1-173
: Comprehensive improvements enhancing reliability and maintainabilityThis refactoring of the schedules module introduces significant improvements across multiple areas:
- Enhanced type safety through explicit type definitions and stricter typing.
- Improved error handling and state management in the
useSchedules
hook.- Better resource management with proper cleanup of subscriptions.
- More flexible query construction with the new
accountSchedulesQuery
function.- Updated context and provider implementations that align with the new hook structure.
These changes collectively result in a more robust, maintainable, and flexible implementation of the schedules functionality. The refactoring aligns well with React best practices and addresses previous concerns while introducing valuable new features.
The migration to a functional component approach and the use of the new
useTransactions
hook (as mentioned in the PR objectives) is well-executed in this file. The changes contribute positively to the overall goal of simplifying transaction fetching across the codebase.packages/desktop-client/src/components/accounts/Balance.jsx (5)
1-1
: LGTM: Import statements cleaned up and updated.The changes in the import statements look good. Unused hooks have been removed, addressing the previous comment about unused imports. The addition of
css
from@emotion/css
and the change toButtonWithLoading
suggest improvements in styling and button functionality.Also applies to: 4-4, 14-14
Line range hint
90-94
: LGTM: Balance calculation logic updated correctly.The changes in the balance calculation logic correctly reflect the switch from 'account' to 'accountId'. The condition and subsequent calculations have been appropriately updated.
117-117
: LGTM: FilteredBalance function parameter renamed for clarity.The change from 'filteredAmount' to 'filteredBalance' in the
FilteredBalance
function improves clarity and consistency. The usage of the parameter has been correctly updated in the component.Also applies to: 122-122
128-128
: LGTM: MoreBalances function updated with accountId for improved specificity.The
MoreBalances
function has been correctly updated to includeaccountId
in its signature and query names. This change improves the specificity of the queries and aligns with the overall use ofaccountId
throughout the component.Also applies to: 132-133, 136-137
225-228
: LGTM: SelectedBalance and FilteredBalance usage updated correctly.The changes to the
SelectedBalance
andFilteredBalance
component usage look good. TheSelectedBalance
now correctly uses theaccountId
prop, and theFilteredBalance
is conditionally rendered with thefilteredBalance
prop. These changes align with the updates to their respective function signatures and add flexibility to the component.packages/loot-core/src/client/data-hooks/transactions.ts (4)
1-30
: LGTM: Imports and type definitions are well-structuredThe imports and type definitions for the
useTransactions
hook are well-organized and provide a clear interface for the hook's props and results.
32-93
: LGTM: Well-implementeduseTransactions
hook with optimizationsThe
useTransactions
hook is well-implemented, following React best practices. Notable optimizations include:
- Using refs for options and paged query to prevent unnecessary re-renders.
- Proper error handling and cleanup in the effect.
- Correct loading state management using
!!query
.These optimizations align with the learning from PR #3685 about avoiding unnecessary rerenders when options change.
188-236
: LGTM: Well-implementeduseTransactionsSearch
hook and helper functionThe
useTransactionsSearch
hook is concise and follows React best practices. The use of debounce for search input changes is appropriate and will help prevent unnecessary API calls. TheisForPreview
helper function is simple and correctly implemented.
1-236
: Overall: Excellent implementation of transaction-related hooksThis file demonstrates a high-quality implementation of React hooks for managing transactions. Key strengths include:
- Well-structured type definitions for clear interfaces.
- Optimized implementations using refs and memoization to prevent unnecessary re-renders.
- Proper error handling and loading state management across all hooks.
- Effective use of debounce for search functionality.
The code is clean, follows React best practices, and shows attention to performance optimization. The suggested minor optimization for
usePreviewTransactions
could further improve the already solid implementation.packages/desktop-client/src/components/transactions/TransactionList.jsx (3)
60-60
: LGTM: Addition ofisLoading
propThe introduction of the
isLoading
prop with a default value offalse
is a good addition. This will allow for better management of loading states in the component, potentially improving the user experience.
200-200
: LGTM: Propagation ofisLoading
prop toTransactionTable
Passing the
isLoading
prop to theTransactionTable
component is a good practice. This allows the loading state to be propagated down the component tree, enabling theTransactionTable
to respond appropriately to loading states.
Line range hint
1-240
: Overall assessment: Improvements to transaction handling and loading state managementThe changes in this file align well with the PR objectives of migrating to functional components and improving state management. The modifications to the
onSave
function enhance the handling of date changes in transactions, ensuring proper sorting and UI updates. The introduction of theisLoading
prop and its propagation to child components improves the management of loading states throughout the transaction-related components.These changes contribute positively to the codebase by improving user experience and maintaining data consistency. The code is well-structured and the modifications are thoughtful and purposeful.
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6)
2-8
: LGTM! Improved type definitions and performance optimizations.The changes in imports and type definitions are well-considered:
- Adding
useCallback
paves the way for performance optimizations.- The new
isLoading
prop inSchedulesTableProps
aligns with the PR objective of simplifying transaction fetching.- Changing
schedules
toreadonly ScheduleEntity[]
enforces immutability, which is a good practice for preventing unintended mutations.These updates enhance type safety and set the stage for performance improvements.
Also applies to: 39-40
Line range hint
261-276
: LGTM! Consistent use of readonly type.The change to
readonly SchedulesTableItem[]
for theitems
variable is consistent with the earlier modification to theschedules
prop. This maintains immutability throughout the component, which is a good practice for preventing unintended mutations and potential bugs.The existing logic for handling completed schedules is preserved, ensuring that the component's functionality remains intact.
278-343
: LGTM! Performance optimization with useCallback.The refactoring of
renderSchedule
to useuseCallback
is a good performance optimization:
- It memoizes the function, preventing unnecessary re-renders of child components that depend on this function.
- The dependencies array is correctly specified, ensuring the function is only recreated when necessary.
The core logic of the function remains unchanged, preserving its functionality while improving performance. The minor style adjustments (lines 288-289) are appropriate and don't affect the component's behavior.
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
345-375
: LGTM! Consistent performance optimization.The refactoring of
renderItem
to useuseCallback
is consistent with the earlier optimization ofrenderSchedule
:
- It further enhances performance by memoizing this function as well.
- The dependency array correctly includes only
renderSchedule
, as it's the only external dependency used in the function.This change maintains the component's functionality while improving its overall performance characteristics.
Line range hint
406-412
: LGTM! Improved loading state handling.The addition of the
loading
prop to theTable
component is a valuable improvement:
- It aligns with the new
isLoading
prop added toSchedulesTableProps
.- This change allows the
Table
component to handle loading states, which can significantly improve user experience by providing visual feedback during data fetching.This modification directly supports the PR objective of simplifying transaction fetching across the codebase.
Line range hint
1-415
: Overall, excellent improvements to SchedulesTable component!This refactoring successfully achieves the PR objectives:
- The component has been effectively migrated to a functional component.
- Performance optimizations have been implemented through the use of
useCallback
.- Type safety has been improved with the addition of
readonly
modifiers and the newisLoading
prop.- The changes support better handling of loading states, which aligns with the goal of simplifying transaction fetching.
These modifications enhance the component's performance, maintainability, and user experience. Great job on this refactoring!
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/loot-core/src/client/query-helpers.ts (5)
4-4
: Improved type safety and readability with new imports and type definitions.The addition of the
Query
type import and the new type definitions (Data
,Listener
,LiveQueryOptions
, andPagedQueryOptions
) significantly enhance the overall type safety and readability of the code. These changes provide better structure and reusability across the file.Also applies to: 48-57, 242-244
10-22
: EnhancedliveQuery
function with improved type safety and flexibility.The
liveQuery
function has been significantly improved:
- Use of generics (
<TResponse>
) allows for flexible response types.- Structured options object enhances readability and maintainability.
- Clear return type
LiveQuery<TResponse>
improves type safety.These changes make the function more robust and easier to use correctly.
25-45
: Consistent improvements inpagedQuery
function.The
pagedQuery
function has been updated consistently withliveQuery
:
- Use of generics for flexible response types.
- Structured options object for better readability.
- Clear return type
PagedQuery<TResponse>
.This consistency in approach across related functions improves the overall coherence of the API.
Line range hint
247-426
: EnhancedPagedQuery
class with improved pagination handling and type safety.The
PagedQuery
class has been significantly improved:
- Extends
LiveQuery
with generics for better type consistency.- Robust pagination handling with new properties and methods.
- Refactored
run
,refetchUpToRow
, andfetchNext
methods for better efficiency.- Use of
once
forfetchNext
prevents multiple simultaneous fetches, which is a good optimization.optimisticUpdate
now correctly updates the total count.These enhancements make the pagination logic more reliable and efficient.
🧰 Tools
🪛 Biome
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
Line range hint
1-426
: Excellent refactoring with significant improvements in type safety and structure.This refactoring represents a substantial improvement to the codebase:
- Consistent use of TypeScript generics and new type definitions enhance type safety throughout.
- Restructured function signatures with options objects improve API clarity and flexibility.
- Refactored class implementations, particularly in
LiveQuery
andPagedQuery
, provide better separation of concerns and more robust functionality.- Pagination handling in
PagedQuery
is now more efficient and reliable.These changes align well with modern TypeScript best practices and should significantly improve code maintainability and reduce the likelihood of runtime errors. Great work on this refactoring effort!
🧰 Tools
🪛 Biome
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7)
8-8
: New hook imported for category management.The
useCategories
hook has been imported, indicating a shift towards a more centralized category management approach. This change aligns with best practices for state management in React components.
Line range hint
141-151
: Updated category parsing logic.The
parseCategoryFields
function has been modified to work with the new category structure. It now iterates through the categories to find a match based on either ID or name. This change improves the flexibility of category matching.
158-159
: Improved prop handling and category management.The component now directly destructures
accountId
andonImported
from theoptions
prop, simplifying the interface. TheuseCategories
hook is used to obtain the list of categories, which centralizes category management and reduces prop drilling.
243-246
: Enhanced category handling in import preview.The
getImportPreview
function now uses theparseCategoryFields
helper with the new category structure, improving consistency in category handling. The dependency array foruseCallback
has been correctly updated to includecategories
, ensuring the function is recreated when categories change.Also applies to: 313-313
587-588
: Consistent category handling in import process.The
onImport
function now uses theparseCategoryFields
helper with the new category structure, ensuring consistency with the preview functionality. This change maintains a uniform approach to category handling throughout the import process.
789-789
: Updated category prop passing to Transaction component.The
categories
prop is now directly passed to theTransaction
component, reflecting the centralized category management approach. This change ensures that the child component has access to the most up-to-date category information.
Line range hint
1-1037
: Overall improvement in category management and component structure.The changes in this file represent a significant improvement in how categories are managed within the
ImportTransactionsModal
component. By introducing theuseCategories
hook and consistently updating category handling throughout the component, the code now follows a more centralized and efficient approach to category management. This refactoring enhances maintainability, reduces prop drilling, and ensures consistent category handling across different functions within the component.Key improvements include:
- Centralized category management using the
useCategories
hook.- Consistent use of the
parseCategoryFields
helper function.- Updated dependency arrays in
useCallback
hooks to properly react to category changes.- Simplified prop handling and passing to child components.
These changes align well with React best practices and should improve the overall performance and maintainability of the component.
packages/desktop-client/src/components/table.tsx (4)
819-819
: New propisLoading
added toSelectedItemsButtonProps
The
isLoading
prop has been added to theSelectedItemsButtonProps
type. This is a good addition as it allows the component to handle loading states.
827-827
: Default value forisLoading
propThe
isLoading
prop is given a default value offalse
in the function parameters. This is a good practice as it maintains backward compatibility with existing usage of the component.
Line range hint
842-856
: ButtonWithLoading component used instead of ButtonThe
Button
component has been replaced withButtonWithLoading
, which now accepts theisLoading
prop. This change allows the button to display a loading state when necessary.A few observations:
- The
isLoading
prop is correctly passed to theButtonWithLoading
component.- The
ref
is properly forwarded usingtriggerRef
.- The
onPress
handler now sets themenuOpen
state directly, which is an improvement in terms of readability and performance compared to using a callback function.These changes enhance the functionality of the component while maintaining its existing behavior.
Line range hint
819-856
: Summary: Loading state support added to SelectedItemsButtonThe changes introduce loading state support to the
SelectedItemsButton
component. This is achieved by:
- Adding an
isLoading
prop toSelectedItemsButtonProps
.- Using the
ButtonWithLoading
component instead ofButton
.- Passing the
isLoading
prop toButtonWithLoading
.These changes enhance the component's functionality and user experience by providing visual feedback during asynchronous operations. The implementation is clean, follows React best practices, and maintains backward compatibility.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6)
59-63
: Efficient use ofuseMemo
and updatedSchedulesProvider
Great job refactoring to use
useMemo
forschedulesQuery
. The dependency array is correctly specified with[accountId]
, ensuring the memoized value updates appropriately whenaccountId
changes. UpdatingSchedulesProvider
to accept thequery
prop aligns with the new API changes and enhances code readability.Also applies to: 81-81
106-111
: Proper implementation ofuseCallback
with accurate dependenciesThe callbacks
onSave
,onEditNotes
,onCloseAccount
, andonReopenAccount
are correctly wrapped withuseCallback
. The dependency arrays include all necessary dependencies, which optimizes performance by preventing unnecessary re-renders.Also applies to: 117-128, 130-136, 138-155
217-222
: Expanded types foraccountId
andaccountName
Modifying the types of
accountId
andaccountName
to include specific string literals enhances the flexibility of the component. This allowsTransactionListWithPreviews
to handle special account views like 'budgeted', 'offbudget', and 'uncategorized' seamlessly.
315-318
: VariabletransactionsToDisplay
is correctly implementedGood catch on correcting the variable name to
transactionsToDisplay
. This ensures consistency and readability in your code.
321-322
: Proper loading states and transaction handlingCombining
isLoading
andisPreviewTransactionsLoading
for theisLoading
prop ensures that the loading state accurately reflects all data fetching. PassingtransactionsToDisplay
to thetransactions
prop correctly includes both preview and regular transactions when not searching.Also applies to: 326-326
226-229
: EnsureaccountId
is handled correctly when undefinedThe
baseTransactionsQuery
function usesaccountId
, which might beundefined
. Please verify thatqueries.transactions(accountId)
can handle anundefined
value without causing errors or unintended behavior.You can run the following script to check how
queries.transactions
handles an undefinedaccountId
:This script searches for the
transactions
function definition to inspect how it handlesaccountId
. Ensure that the function accounts forundefined
values appropriately.
{schedule._date && schedule._date.frequency && ( | ||
<SvgCheck style={{ width: 13, height: 13 }} /> | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using optional chaining for improved readability.
As mentioned in previous review comments, you can simplify the condition using optional chaining. This change will make the code more concise without altering its behavior.
Apply this diff to simplify the condition:
- {schedule._date && schedule._date.frequency && (
+ {schedule._date?.frequency && (
<SvgCheck style={{ width: 13, height: 13 }} />
)}
This change maintains the same logic while making the code more readable and concise.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
{schedule._date && schedule._date.frequency && ( | |
<SvgCheck style={{ width: 13, height: 13 }} /> | |
)} | |
{schedule._date?.frequency && ( | |
<SvgCheck style={{ width: 13, height: 13 }} /> | |
)} |
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
} catch (e) { | ||
console.log('Error fetching data', e); | ||
this.onError(e); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider enhancing error handling and logging.
While the current error handling in fetchData
is functional, it could be improved:
- Consider classifying errors (e.g., network errors, data errors) for more targeted handling.
- Log more details about the error, such as the query that caused it, to aid in debugging.
- Implement a retry mechanism for transient errors.
Here's a potential enhancement:
try {
// ... existing code ...
} catch (e) {
console.error('Error fetching data:', {
error: e,
query: this._query.serialize(),
requestId: reqId
});
if (e instanceof NetworkError && this._retryCount < MAX_RETRIES) {
this._retryCount++;
await this.fetchData(runQuery);
} else {
this.onError(e);
}
}
This suggestion adds more detailed logging, error classification, and a basic retry mechanism. Adjust according to your specific error types and retry policy.
// to only react to remote syncs. By default, queries will | ||
// always update to all changes. | ||
// | ||
// TODO: errors? | ||
const syncTypes = this.onlySync ? ['success'] : ['applied', 'success']; | ||
|
||
if (syncTypes.indexOf(type) !== -1) { | ||
if (this._supportedSyncTypes.has(type)) { | ||
this.onUpdate(tables); | ||
} | ||
}); | ||
} | ||
}; | ||
|
||
unsubscribe = () => { | ||
if (this._unsubscribe) { | ||
this._unsubscribe(); | ||
this._unsubscribe = null; | ||
if (this._unsubscribeSyncEvent) { | ||
this._unsubscribeSyncEvent(); | ||
this._unsubscribeSyncEvent = null; | ||
} | ||
}; | ||
|
||
mapData = data => { | ||
if (this.mapper) { | ||
return this.mapper(data); | ||
} | ||
return data; | ||
protected _optimisticUpdate = ( | ||
updateFn: (data: Data<TResponse>) => Data<TResponse>, | ||
) => { | ||
const previousData = this.data; | ||
this.updateData(updateFn); | ||
this.onData(this.data, previousData); | ||
}; | ||
|
||
getQuery = () => { | ||
return this.query; | ||
optimisticUpdate = (dataFunc: (data: Data<TResponse>) => Data<TResponse>) => { | ||
this._optimisticUpdate(dataFunc); | ||
}; | ||
|
||
getData = () => { | ||
return this.mappedData; | ||
protected updateData = ( | ||
updateFn: (data: Data<TResponse>) => Data<TResponse>, | ||
) => { | ||
this.data = updateFn(this.data); | ||
}; | ||
|
||
getNumListeners = () => { | ||
return this.listeners.length; | ||
}; | ||
protected fetchData = async ( | ||
runQuery: () => Promise<{ | ||
data: Data<TResponse>; | ||
dependencies: Set<string>; | ||
}>, | ||
) => { | ||
// TODO: precompile queries, or cache compilation results on the | ||
// backend. could give a query an id which makes it cacheable via | ||
// an LRU cache | ||
|
||
isRunning = () => { | ||
return this._unsubscribe != null; | ||
}; | ||
const reqId = Math.random(); | ||
this.inflightRequestId = reqId; | ||
|
||
_optimisticUpdate = (dataFunc, mappedDataFunc) => { | ||
this.data = dataFunc(this.data); | ||
this.mappedData = mappedDataFunc(this.mappedData); | ||
}; | ||
try { | ||
const { data, dependencies } = await runQuery(); | ||
|
||
optimisticUpdate = (dataFunc, mappedDataFunc) => { | ||
const prevMappedData = this.mappedData; | ||
this._optimisticUpdate(dataFunc, mappedDataFunc); | ||
this.onData(this.mappedData, prevMappedData); | ||
// Regardless if this request was cancelled or not, save the | ||
// dependencies. The query can't change so all requests will | ||
// return the same deps. | ||
if (this._dependencies == null) { | ||
this._dependencies = new Set(dependencies); | ||
} | ||
|
||
// Only fire events if this hasn't been cancelled and if we're | ||
// still subscribed (`this.unsubscribeSyncEvent` will exist) | ||
if (this.inflightRequestId === reqId && this._unsubscribeSyncEvent) { | ||
const previousData = this.data; | ||
this.data = data; | ||
this.onData(this.data, previousData); | ||
this.inflightRequestId = null; | ||
} | ||
} catch (e) { | ||
console.log('Error fetching data', e); | ||
this.onError(e); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Significant improvements in LiveQuery
class structure and type safety.
The LiveQuery
class has been substantially enhanced:
- Use of generics improves type safety and flexibility.
- Refactored
fetchData
method is more robust, handling cancellation and error cases effectively. - Clear separation of concerns with new private methods and properties.
These changes make the class more maintainable and less prone to errors.
However, there's a minor issue to address:
The type of _unsubscribeSyncEvent
can be improved for clarity. Apply this change:
-private _unsubscribeSyncEvent: () => void | null;
+private _unsubscribeSyncEvent: (() => void) | null;
This change clarifies that _unsubscribeSyncEvent
is either a function returning void or null, not a function returning void or null.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export class LiveQuery<TResponse = unknown> { | |
private _unsubscribeSyncEvent: () => void | null; | |
private _data: Data<TResponse>; | |
private _dependencies: Set<string>; | |
private _listeners: Array<Listener<TResponse>>; | |
private _supportedSyncTypes: Set<string>; | |
private _query: Query; | |
private _onError: (error: Error) => void; | |
get query() { | |
return this._query; | |
} | |
get data() { | |
return this._data; | |
} | |
private set data(data: Data<TResponse>) { | |
this._data = data; | |
} | |
get isRunning() { | |
return this._unsubscribeSyncEvent != null; | |
} | |
// Async coordination | |
inflight; | |
inflightRequestId; | |
restart; | |
private _inflightRequestId: number | null; | |
protected get inflightRequestId() { | |
return this._inflightRequestId; | |
} | |
private set inflightRequestId(id: number) { | |
this._inflightRequestId = id; | |
} | |
constructor( | |
query, | |
onData?: (response: Response) => void, | |
opts: { mapper?; onlySync?: boolean } = {}, | |
query: Query, | |
onData: Listener<TResponse>, | |
onError?: (error: Error) => void, | |
options: LiveQueryOptions = {}, | |
) { | |
this.error = new Error(); | |
this.query = query; | |
this.data = null; | |
this.mappedData = null; | |
this.dependencies = null; | |
this.mapper = opts.mapper; | |
this.onlySync = opts.onlySync; | |
this.listeners = []; | |
// Async coordination | |
this.inflight = false; | |
this.restart = false; | |
this._query = query; | |
this._data = null; | |
this._dependencies = null; | |
this._listeners = []; | |
this._onError = onError || (() => {}); | |
// TODO: error types? | |
this._supportedSyncTypes = options.onlySync | |
? new Set<string>(['success']) | |
: new Set<string>(['applied', 'success']); | |
if (onData) { | |
this.addListener(onData); | |
} | |
} | |
addListener(func) { | |
this.listeners.push(func); | |
addListener = (func: Listener<TResponse>) => { | |
this._listeners.push(func); | |
return () => { | |
this.listeners = this.listeners.filter(l => l !== func); | |
this._listeners = this._listeners.filter(l => l !== func); | |
}; | |
} | |
}; | |
onData = (data, prevData) => { | |
for (let i = 0; i < this.listeners.length; i++) { | |
this.listeners[i](data, prevData); | |
protected onData = (data: Data<TResponse>, prevData: Data<TResponse>) => { | |
for (let i = 0; i < this._listeners.length; i++) { | |
this._listeners[i](data, prevData); | |
} | |
}; | |
onUpdate = tables => { | |
protected onError = (error: Error) => { | |
this._onError(error); | |
}; | |
protected onUpdate = (tables: string[]) => { | |
// We might not know the dependencies if the first query result | |
// hasn't come back yet | |
if ( | |
this.dependencies == null || | |
tables.find(table => this.dependencies.has(table)) | |
this._dependencies == null || | |
tables.find(table => this._dependencies.has(table)) | |
) { | |
this.run(); | |
} | |
}; | |
run = () => { | |
this.subscribe(); | |
return this._fetchData(() => runQuery(this.query)); | |
return this.fetchData(() => runQuery(this._query)); | |
}; | |
_fetchData = async makeRequest => { | |
// TODO: precompile queries, or cache compilation results on the | |
// backend. could give a query an id which makes it cacheable via | |
// an LRU cache | |
const reqId = Math.random(); | |
this.inflightRequestId = reqId; | |
const { data, dependencies } = await makeRequest(); | |
// Regardless if this request was cancelled or not, save the | |
// dependencies. The query can't change so all requests will | |
// return the same deps. | |
if (this.dependencies == null) { | |
this.dependencies = new Set(dependencies); | |
} | |
// Only fire events if this hasn't been cancelled and if we're | |
// still subscribed (`this._subscribe` will exist) | |
if (this.inflightRequestId === reqId && this._unsubscribe) { | |
const prevData = this.mappedData; | |
this.data = data; | |
this.mappedData = this.mapData(data); | |
this.onData(this.mappedData, prevData); | |
this.inflightRequestId = null; | |
} | |
static runLiveQuery = <TResponse>( | |
query: Query, | |
onData: Listener<TResponse>, | |
onError: (error: Error) => void, | |
options: LiveQueryOptions = {}, | |
) => { | |
const liveQuery = new LiveQuery<TResponse>(query, onData, onError, options); | |
liveQuery.run(); | |
return liveQuery; | |
}; | |
subscribe = () => { | |
if (this._unsubscribe == null) { | |
this._unsubscribe = listen('sync-event', ({ type, tables }) => { | |
protected subscribe = () => { | |
if (this._unsubscribeSyncEvent == null) { | |
this._unsubscribeSyncEvent = listen('sync-event', ({ type, tables }) => { | |
// If the user is doing optimistic updates, they don't want to | |
// always refetch whenever something changes because it would | |
// refetch all data after they've already updated the UI. This | |
// voids the perf benefits of optimistic updates. Allow querys | |
// to only react to remote syncs. By default, queries will | |
// always update to all changes. | |
// | |
// TODO: errors? | |
const syncTypes = this.onlySync ? ['success'] : ['applied', 'success']; | |
if (syncTypes.indexOf(type) !== -1) { | |
if (this._supportedSyncTypes.has(type)) { | |
this.onUpdate(tables); | |
} | |
}); | |
} | |
}; | |
unsubscribe = () => { | |
if (this._unsubscribe) { | |
this._unsubscribe(); | |
this._unsubscribe = null; | |
if (this._unsubscribeSyncEvent) { | |
this._unsubscribeSyncEvent(); | |
this._unsubscribeSyncEvent = null; | |
} | |
}; | |
mapData = data => { | |
if (this.mapper) { | |
return this.mapper(data); | |
} | |
return data; | |
protected _optimisticUpdate = ( | |
updateFn: (data: Data<TResponse>) => Data<TResponse>, | |
) => { | |
const previousData = this.data; | |
this.updateData(updateFn); | |
this.onData(this.data, previousData); | |
}; | |
getQuery = () => { | |
return this.query; | |
optimisticUpdate = (dataFunc: (data: Data<TResponse>) => Data<TResponse>) => { | |
this._optimisticUpdate(dataFunc); | |
}; | |
getData = () => { | |
return this.mappedData; | |
protected updateData = ( | |
updateFn: (data: Data<TResponse>) => Data<TResponse>, | |
) => { | |
this.data = updateFn(this.data); | |
}; | |
getNumListeners = () => { | |
return this.listeners.length; | |
}; | |
protected fetchData = async ( | |
runQuery: () => Promise<{ | |
data: Data<TResponse>; | |
dependencies: Set<string>; | |
}>, | |
) => { | |
// TODO: precompile queries, or cache compilation results on the | |
// backend. could give a query an id which makes it cacheable via | |
// an LRU cache | |
isRunning = () => { | |
return this._unsubscribe != null; | |
}; | |
const reqId = Math.random(); | |
this.inflightRequestId = reqId; | |
_optimisticUpdate = (dataFunc, mappedDataFunc) => { | |
this.data = dataFunc(this.data); | |
this.mappedData = mappedDataFunc(this.mappedData); | |
}; | |
try { | |
const { data, dependencies } = await runQuery(); | |
optimisticUpdate = (dataFunc, mappedDataFunc) => { | |
const prevMappedData = this.mappedData; | |
this._optimisticUpdate(dataFunc, mappedDataFunc); | |
this.onData(this.mappedData, prevMappedData); | |
// Regardless if this request was cancelled or not, save the | |
// dependencies. The query can't change so all requests will | |
// return the same deps. | |
if (this._dependencies == null) { | |
this._dependencies = new Set(dependencies); | |
} | |
// Only fire events if this hasn't been cancelled and if we're | |
// still subscribed (`this.unsubscribeSyncEvent` will exist) | |
if (this.inflightRequestId === reqId && this._unsubscribeSyncEvent) { | |
const previousData = this.data; | |
this.data = data; | |
this.onData(this.data, previousData); | |
this.inflightRequestId = null; | |
} | |
} catch (e) { | |
console.log('Error fetching data', e); | |
this.onError(e); | |
} | |
export class LiveQuery<TResponse = unknown> { | |
private _unsubscribeSyncEvent: (() => void) | null; | |
private _data: Data<TResponse>; | |
private _dependencies: Set<string>; | |
private _listeners: Array<Listener<TResponse>>; | |
private _supportedSyncTypes: Set<string>; | |
private _query: Query; | |
private _onError: (error: Error) => void; | |
get query() { | |
return this._query; | |
} | |
get data() { | |
return this._data; | |
} | |
private set data(data: Data<TResponse>) { | |
this._data = data; | |
} | |
get isRunning() { | |
return this._unsubscribeSyncEvent != null; | |
} | |
// Async coordination | |
private _inflightRequestId: number | null; | |
protected get inflightRequestId() { | |
return this._inflightRequestId; | |
} | |
private set inflightRequestId(id: number) { | |
this._inflightRequestId = id; | |
} | |
constructor( | |
query: Query, | |
onData: Listener<TResponse>, | |
onError?: (error: Error) => void, | |
options: LiveQueryOptions = {}, | |
) { | |
this._query = query; | |
this._data = null; | |
this._dependencies = null; | |
this._listeners = []; | |
this._onError = onError || (() => {}); | |
// TODO: error types? | |
this._supportedSyncTypes = options.onlySync | |
? new Set<string>(['success']) | |
: new Set<string>(['applied', 'success']); | |
if (onData) { | |
this.addListener(onData); | |
} | |
} | |
addListener = (func: Listener<TResponse>) => { | |
this._listeners.push(func); | |
return () => { | |
this._listeners = this._listeners.filter(l => l !== func); | |
}; | |
}; | |
protected onData = (data: Data<TResponse>, prevData: Data<TResponse>) => { | |
for (let i = 0; i < this._listeners.length; i++) { | |
this._listeners[i](data, prevData); | |
} | |
}; | |
protected onError = (error: Error) => { | |
this._onError(error); | |
}; | |
protected onUpdate = (tables: string[]) => { | |
// We might not know the dependencies if the first query result | |
// hasn't come back yet | |
if ( | |
this._dependencies == null || | |
tables.find(table => this._dependencies.has(table)) | |
) { | |
this.run(); | |
} | |
}; | |
run = () => { | |
this.subscribe(); | |
return this.fetchData(() => runQuery(this._query)); | |
}; | |
static runLiveQuery = <TResponse>( | |
query: Query, | |
onData: Listener<TResponse>, | |
onError: (error: Error) => void, | |
options: LiveQueryOptions = {}, | |
) => { | |
const liveQuery = new LiveQuery<TResponse>(query, onData, onError, options); | |
liveQuery.run(); | |
return liveQuery; | |
}; | |
protected subscribe = () => { | |
if (this._unsubscribeSyncEvent == null) { | |
this._unsubscribeSyncEvent = listen('sync-event', ({ type, tables }) => { | |
// If the user is doing optimistic updates, they don't want to | |
// always refetch whenever something changes because it would | |
// refetch all data after they've already updated the UI. This | |
// voids the perf benefits of optimistic updates. Allow querys | |
// to only react to remote syncs. By default, queries will | |
// always update to all changes. | |
if (this._supportedSyncTypes.has(type)) { | |
this.onUpdate(tables); | |
} | |
}); | |
} | |
}; | |
unsubscribe = () => { | |
if (this._unsubscribeSyncEvent) { | |
this._unsubscribeSyncEvent(); | |
this._unsubscribeSyncEvent = null; | |
} | |
}; | |
protected _optimisticUpdate = ( | |
updateFn: (data: Data<TResponse>) => Data<TResponse>, | |
) => { | |
const previousData = this.data; | |
this.updateData(updateFn); | |
this.onData(this.data, previousData); | |
}; | |
optimisticUpdate = (dataFunc: (data: Data<TResponse>) => Data<TResponse>) => { | |
this._optimisticUpdate(dataFunc); | |
}; | |
protected updateData = ( | |
updateFn: (data: Data<TResponse>) => Data<TResponse>, | |
) => { | |
this.data = updateFn(this.data); | |
}; | |
protected fetchData = async ( | |
runQuery: () => Promise<{ | |
data: Data<TResponse>; | |
dependencies: Set<string>; | |
}>, | |
) => { | |
// TODO: precompile queries, or cache compilation results on the | |
// backend. could give a query an id which makes it cacheable via | |
// an LRU cache | |
const reqId = Math.random(); | |
this.inflightRequestId = reqId; | |
try { | |
const { data, dependencies } = await runQuery(); | |
// Regardless if this request was cancelled or not, save the | |
// dependencies. The query can't change so all requests will | |
// return the same deps. | |
if (this._dependencies == null) { | |
this._dependencies = new Set(dependencies); | |
} | |
// Only fire events if this hasn't been cancelled and if we're | |
// still subscribed (`this.unsubscribeSyncEvent` will exist) | |
if (this.inflightRequestId === reqId && this._unsubscribeSyncEvent) { | |
const previousData = this.data; | |
this.data = data; | |
this.onData(this.data, previousData); | |
this.inflightRequestId = null; | |
} | |
} catch (e) { | |
console.log('Error fetching data', e); | |
this.onError(e); | |
} |
🧰 Tools
🪛 Biome
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
useEffect(() => { | ||
if (accountId) { | ||
dispatch(markAccountRead(accountId)); | ||
} | ||
}, [accountId, dispatch]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using useLayoutEffect
for synchronous updates
Based on previous implementations, such as the AllTransactions
component where useLayoutEffect
is used to dispatch actions synchronously before painting, consider using useLayoutEffect
instead of useEffect
for markAccountRead
. This ensures that the account is marked as read before the component renders, preventing any potential UI inconsistencies.
Suggestion:
-useEffect(() => {
+useLayoutEffect(() => {
if (accountId) {
dispatch(markAccountRead(accountId));
}
-}, [accountId, dispatch]);
+}, [accountId, dispatch]);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
useEffect(() => { | |
if (accountId) { | |
dispatch(markAccountRead(accountId)); | |
} | |
}, [accountId, dispatch]); | |
useLayoutEffect(() => { | |
if (accountId) { | |
dispatch(markAccountRead(accountId)); | |
} | |
}, [accountId, dispatch]); |
const { isSearching, search: onSearch } = useTransactionsSearch({ | ||
updateQuery: setTransactionsQuery, | ||
resetQuery: () => setTransactionsQuery(baseTransactionsQuery()), | ||
dateFormat, | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verify dependencies in useTransactionsSearch
The useTransactionsSearch
hook uses baseTransactionsQuery
within resetQuery
, but baseTransactionsQuery
is defined outside of the hook without dependencies. To ensure resetQuery
always has the latest baseTransactionsQuery
, consider wrapping resetQuery
with useCallback
and adding baseTransactionsQuery
to its dependency array.
Suggestion:
+const resetQuery = useCallback(() => {
+ setTransactionsQuery(baseTransactionsQuery());
+}, [baseTransactionsQuery]);
const { isSearching, search: onSearch } = useTransactionsSearch({
updateQuery: setTransactionsQuery,
- resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
+ resetQuery,
dateFormat,
});
Committable suggestion was skipped due to low confidence.
d0640a7
to
ac752c6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Outside diff range and nitpick comments (24)
packages/loot-core/src/client/data-hooks/filters.ts (2)
Line range hint
1-7
: Consider removing@ts-strict-ignore
The presence of
@ts-strict-ignore
suggests potential type safety issues. Since TypeScript types are being used throughout the file (e.g.,TransactionFilterEntity
), consider addressing the type issues and enabling strict mode for better type safety.
Line range hint
8-20
: Consider renamingtoJS
to better reflect its purposeThe function name
toJS
doesn't clearly convey its purpose of mapping database rows toTransactionFilterEntity
objects. Consider a more descriptive name likemapRowsToFilters
ortransformToFilterEntities
.-function toJS(rows): TransactionFilterEntity[] { +function mapRowsToFilters(rows): TransactionFilterEntity[] {packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3)
52-67
: Optimize useEffect dependenciesThe sync event handler properly manages table updates and cleanup. However, consider extracting the table check conditions to memoized values to optimize the effect's dependency array.
+ const shouldReloadTransactions = useMemo( + () => (tables) => ( + tables.includes('transactions') || + tables.includes('category_mapping') || + tables.includes('payee_mapping') + ), + [] + ); + const shouldReloadPayees = useMemo( + () => (tables) => ( + tables.includes('payees') || + tables.includes('payee_mapping') + ), + [] + ); useEffect(() => { return listen('sync-event', ({ type, tables }) => { if (type === 'applied') { - if ( - tables.includes('transactions') || - tables.includes('category_mapping') || - tables.includes('payee_mapping') - ) { + if (shouldReloadTransactions(tables)) { reloadTransactions?.(); } - if (tables.includes('payees') || tables.includes('payee_mapping')) { + if (shouldReloadPayees(tables)) { dispatch(getPayees()); } } }); - }, [dispatch, reloadTransactions]); + }, [dispatch, reloadTransactions, shouldReloadTransactions, shouldReloadPayees]);
69-73
: Add JSDoc comments for search configurationConsider adding documentation to clarify the search configuration parameters and their impact.
+ /** + * Configures transaction search functionality + * @param {Object} config + * @param {Function} config.updateQuery - Updates the transaction query based on search + * @param {Function} config.resetQuery - Resets the query to its base state + * @param {string} config.dateFormat - Format used for date-based searches + */ const { search: onSearch } = useTransactionsSearch({ updateQuery: setTransactionsQuery, resetQuery: () => setTransactionsQuery(baseTransactionsQuery()), dateFormat, });
75-82
: Enhance documentation of preview transaction handlingThe comment about preview transactions could be more descriptive to help future maintainers.
- // details of how the native app used to handle preview transactions here can be found at commit 05e58279 + /** + * Opens the transaction detail view. + * Preview transactions are skipped as they're temporary (see commit 05e58279). + * @param {Object} transaction - The transaction to open + */packages/loot-core/src/shared/query.ts (2)
Line range hint
25-40
: Consider enhancing type safety for tableOptions initialization.While the constructor implementation is good, the tableOptions initialization could be more type-safe.
Consider this improvement:
- tableOptions: state.tableOptions || {}, + tableOptions: state.tableOptions ?? {} as Record<string, unknown>,
Line range hint
145-164
: Consider adding order value validation in getPrimaryOrderBy.The function handles different order expression formats well, but it doesn't validate that the order value is either 'asc' or 'desc'.
Consider adding validation:
const [field] = Object.keys(firstOrder); - return { field, order: firstOrder[field] }; + const order = firstOrder[field]; + if (order !== 'asc' && order !== 'desc') { + throw new Error(`Invalid order value: ${order}. Expected 'asc' or 'desc'`); + } + return { field, order };packages/loot-core/src/client/data-hooks/schedules.tsx (1)
151-173
: Consider adding JSDoc documentationThe
accountSchedulesQuery
function would benefit from JSDoc documentation explaining:
- The purpose of the function
- The meaning of each accountId value ('budgeted', 'offbudget', 'uncategorized')
- The structure of the returned query
+/** + * Creates a query to fetch schedules filtered by account + * @param accountId - The account to filter by: + * - undefined: fetch all schedules + * - 'budgeted': fetch schedules for budgeted accounts + * - 'offbudget': fetch schedules for off-budget accounts + * - 'uncategorized': fetch schedules with no next date + * - string: fetch schedules for a specific account ID + * @returns A query object ordered by next_date in descending order + */ export function accountSchedulesQuery( accountId?: AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized', ) {packages/desktop-client/src/components/accounts/Balance.jsx (1)
157-157
: Remove commented-out code.These commented-out lines about
transactionsQuery
should be removed as they add noise to the codebase.- // const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' }); - // transactionsQuery={transactionsQuery}Also applies to: 220-221
packages/loot-core/src/client/data-hooks/transactions.ts (1)
60-63
: Enhance error message for invalid query.The error message could be more descriptive to help developers understand what went wrong.
if (query.state.table !== 'transactions') { - setError(new Error('Query must be a transactions query.')); + setError(new Error('Invalid query: Expected table "transactions" but got "' + query.state.table + '".')); return; }packages/loot-core/src/client/queries.ts (2)
31-34
: LGTM! Consider adding type safety for the field parameter.The type improvements for
accountId
parameter enhance compile-time safety. However, thefield
parameter could also benefit from type safety.Consider adding a type for the field parameter:
- field = 'account', + field: keyof Pick<SheetFields<'account'>, 'account'> = 'account',
Line range hint
83-124
: Consider improving error handling and robustness.The current implementation has several areas where it could be more defensive:
- Date parsing could throw on invalid input
- The
$or
array filtering could potentially be empty- Amount parsing could be more robust
Consider applying these improvements:
export function transactionsSearch( currentQuery: Query, search: string, dateFormat: SyncedPrefs['dateFormat'], ) { const amount = currencyToAmount(search); // Support various date formats let parsedDate; + try { if (getDayMonthRegex(dateFormat).test(search)) { parsedDate = parseDate(search, getDayMonthFormat(dateFormat), new Date()); } else if (getShortYearRegex(dateFormat).test(search)) { parsedDate = parseDate(search, getShortYearFormat(dateFormat), new Date()); } else { parsedDate = parseDate(search, dateFormat, new Date()); } + } catch (e) { + parsedDate = null; + } + const conditions = [ + isDateValid(parsedDate) && { date: dayFromDate(parsedDate) }, + amount != null && { + amount: { $transform: '$abs', $eq: amountToInteger(amount) }, + }, + amount != null && + Number.isInteger(amount) && { + amount: { + $transform: { $abs: { $idiv: ['$', 100] } }, + $eq: amount, + }, + }, + ].filter(Boolean); + return currentQuery.filter({ $or: { 'payee.name': { $like: `%${search}%` }, notes: { $like: `%${search}%` }, 'category.name': { $like: `%${search}%` }, 'account.name': { $like: `%${search}%` }, - $or: [ - isDateValid(parsedDate) && { date: dayFromDate(parsedDate) }, - amount != null && { - amount: { $transform: '$abs', $eq: amountToInteger(amount) }, - }, - amount != null && - Number.isInteger(amount) && { - amount: { - $transform: { $abs: { $idiv: ['$', 100] } }, - $eq: amount, - }, - }, - ].filter(Boolean), + ...(conditions.length > 0 ? { $or: conditions } : {}), }, }); }packages/desktop-client/src/hooks/useSelected.tsx (1)
320-323
: Enhance deprecation notice with migration details.While the deprecation notice is good, consider enhancing it with:
- A brief migration guide or example
- Timeline for removal (if planned)
- Link to documentation (if available)
Example enhancement:
/** * This can be helpful in class components if you cannot use the custom hook * @deprecated Use `SelectedProvider` and `useSelected` instead + * @example + * // Before + * <SelectedProviderWithItems items={items}> + * {children} + * </SelectedProviderWithItems> + * + * // After + * function MyComponent() { + * const selected = useSelected('name', items, initialSelectedIds); + * return ( + * <SelectedProvider instance={selected}> + * {children} + * </SelectedProvider> + * ); + * } + * @see [Migration Guide](link-to-docs) + * @removal Planned for v3.0.0 */packages/loot-core/src/shared/transactions.ts (5)
Line range hint
93-113
: Consider adding runtime validation for transaction ordering.While the code assumes transactions are sorted with parents before children (enforced by the DB layer), consider adding runtime validation to catch potential issues early, especially during testing or development.
function findParentIndex( transactions: readonly TransactionEntity[], idx: number, ) { + // Validate that transactions are properly ordered + if (process.env.NODE_ENV !== 'production') { + const isOrdered = transactions.every((t, i) => + !t.is_child || transactions.some((p, pi) => + p.is_parent && p.id === t.parent_id && pi < i + ) + ); + if (!isOrdered) { + console.warn('Transactions are not properly ordered: parents must come before children'); + } + } // This relies on transactions being sorted in a way where parents // are always before children, which is enforced in the db layer.
Line range hint
123-139
: Consider optimizing array operations for better performance.The current implementation uses
reduce
with array push operations, which can be less efficient for large transaction sets. Consider usingflatMap
for a more functional and potentially more efficient approach.export function ungroupTransactions( transactions: readonly TransactionEntity[], ) { - return transactions.reduce<TransactionEntity[]>((list, parent) => { + return transactions.flatMap(parent => { const { subtransactions, ...trans } = parent; const _subtransactions = subtransactions || []; - - list.push(trans); - - for (let i = 0; i < _subtransactions.length; i++) { - list.push(_subtransactions[i]); - } - return list; - }, []); + return [trans, ..._subtransactions]; + }); }
Line range hint
163-227
: Enhance error handling and consider splitting responsibilities.The
replaceTransactions
function handles multiple concerns and could benefit from:
- More specific error types instead of generic Error
- Separation into smaller, focused functions
- Better error recovery for edge cases
Consider splitting the function into smaller utilities:
interface TransactionError extends Error { type: 'NOT_FOUND' | 'PARENT_NOT_FOUND'; transactionId: string; } function findTransactionWithParent(transactions: readonly TransactionEntity[], id: string): { transaction: TransactionEntity; parentIndex: number | null; } { // Extract transaction finding logic } function generateTransactionDiff( original: readonly TransactionEntity[], updated: TransactionEntity[] ): ReturnType<typeof diffItems<TransactionEntity>> { // Extract diff generation logic }
Line range hint
355-393
: Improve documentation and clarify complex logic.The
makeAsNonChildTransactions
function would benefit from:
- JSDoc documentation explaining the purpose and behavior
- Clear explanation of the logic for handling remaining child transactions
- Type annotations for intermediate calculations
Add comprehensive documentation:
/** * Converts selected child transactions to standalone transactions while maintaining * the integrity of the remaining parent-child relationships. * * @param childTransactionsToUpdate - Child transactions to convert to standalone * @param transactions - Array of transactions starting with the parent followed by children * @returns Object containing updated transactions and deleted transactions * * @example * const result = makeAsNonChildTransactions([childTrans1], [parentTrans, childTrans1, childTrans2]); * // result.updated = [childTrans1AsNonChild] * // result.deleted = [parentTrans] // if only one child remains */
Line range hint
1-393
: Overall assessment: Approved with suggestions for improvement.The changes to make arrays readonly improve type safety and prevent accidental mutations. The code handles complex transaction relationships well, but consider the following improvements:
- Add comprehensive input validation
- Enhance error handling with specific error types
- Improve documentation, especially for complex logic
- Add runtime checks for development environments
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)
Line range hint
261-275
: Consider simplifying the completed schedules logicThe filtering logic could be more concise while maintaining the same functionality.
const items: readonly SchedulesTableItem[] = useMemo(() => { const unCompletedSchedules = filteredSchedules.filter(s => !s.completed); - if (!allowCompleted) { - return unCompletedSchedules; - } - if (showCompleted) { - return filteredSchedules; - } - const hasCompletedSchedule = filteredSchedules.find(s => s.completed); - if (!hasCompletedSchedule) return unCompletedSchedules; - return [...unCompletedSchedules, { id: 'show-completed' }]; + if (!allowCompleted || showCompleted) { + return allowCompleted ? filteredSchedules : unCompletedSchedules; + } + return filteredSchedules.some(s => s.completed) + ? [...unCompletedSchedules, { id: 'show-completed' }] + : unCompletedSchedules; }, [filteredSchedules, showCompleted, allowCompleted]);
Line range hint
406-412
: Remove unnecessary type casting of itemsThe items array is being unnecessarily cast to
ScheduleEntity[]
. The Table component should accept the union typeSchedulesTableItem
.- items={items as ScheduleEntity[]} + items={items}packages/loot-core/src/client/query-helpers.ts (2)
4-4
: LGTM! Well-structured type definitions with room for minor improvement.The introduction of TypeScript types and generics significantly improves type safety and API clarity. The use of structured parameters and ReadonlyArray is particularly good.
Consider adding JSDoc comments for the exported types to improve documentation:
/** Represents the response data as a read-only array */ type Data<TResponse> = ReadonlyArray<TResponse>; /** Callback for handling data updates with previous state */ type Listener<TResponse = unknown> = ( data: Data<TResponse>, previousData: Data<TResponse>, ) => void;Also applies to: 10-57
Line range hint
383-417
: Address potential race condition in _fetchNext method.While the method attempts to handle concurrent requests, there's still a potential race condition:
- The check for inflightRequestId and data comparison happens after the new data is fetched
- This could lead to data inconsistency if multiple fetchNext calls occur simultaneously
Consider this more robust implementation:
private _fetchNext = async () => { + const currentRequestId = Math.random(); + this.inflightRequestId = currentRequestId; + while (this.inflightRequestId) { await this._fetchDataPromise; } + // Another request has started + if (this.inflightRequestId !== currentRequestId) { + return this._fetchNext(); + } + const previousData = this.data; if (!this._hasReachedEnd) { const { data } = await runQuery( this.query.limit(this._pageCount).offset(previousData.length), ); - // If either there is an existing request in flight or the data - // has already changed underneath it, we can't reliably concat - // the data since it's different now. Need to re-run the whole - // process again - if (this.inflightRequestId || previousData !== this.data) { + // If another request has started or data has changed, retry + if (this.inflightRequestId !== currentRequestId || previousData !== this.data) { return this._fetchNext(); } else { if (data.length === 0) { this._hasReachedEnd = true; } else { this._hasReachedEnd = data.length < this._pageCount; const prevData = this.data; this.updateData(currentData => currentData.concat(data)); this.onPageData(data); this.onData(this.data, prevData); } } } + + if (this.inflightRequestId === currentRequestId) { + this.inflightRequestId = null; + } };packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)
769-776
: LGTM! Good handling of loading state.The changes properly handle the loading state of schedules, preventing potential undefined access. The early return pattern is clean and effective.
Consider destructuring with a default empty array to avoid potential undefined access:
- const { isLoading, schedules = [] } = useCachedSchedules(); + const { isLoading, schedules } = useCachedSchedules() ?? { isLoading: true, schedules: [] };
Line range hint
1765-1775
: LGTM! Good propagation of loading state.The loading state is correctly propagated from TransactionTableInner to the Table component, enabling proper loading state handling.
Consider adding JSDoc or PropTypes documentation for the new isLoading prop:
/** * @typedef {Object} TransactionTableInnerProps * @property {boolean} isLoading - Indicates if the transaction data is currently being loaded * ...other props */Also applies to: 1994-1994
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (2)
upcoming-release-notes/3685.md
is excluded by!**/*.md
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (43)
- .eslintrc.js (1 hunks)
- packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
- packages/desktop-client/e2e/accounts.test.js (2 hunks)
- packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
- packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
- packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
- packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
- packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
- packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
- packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
- packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
- packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
- packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
- packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
- packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
- packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
- packages/desktop-client/src/components/table.tsx (4 hunks)
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
- packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
- packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
- packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
- packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
- packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
- packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
- packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
- packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
- packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
- packages/loot-core/src/client/queries.ts (2 hunks)
- packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
- packages/loot-core/src/client/query-helpers.ts (4 hunks)
- packages/loot-core/src/client/query-hooks.ts (1 hunks)
- packages/loot-core/src/shared/query.ts (9 hunks)
- packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useSchedules.ts
🚧 Files skipped from review as they are similar to previous changes (23)
- packages/desktop-client/e2e/accounts.mobile.test.js
- packages/desktop-client/e2e/accounts.test.js
- packages/desktop-client/e2e/page-models/account-page.js
- packages/desktop-client/e2e/page-models/mobile-account-page.js
- packages/desktop-client/e2e/page-models/mobile-accounts-page.js
- packages/desktop-client/src/components/ManageRules.tsx
- packages/desktop-client/src/components/accounts/Header.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
- packages/desktop-client/src/components/modals/EditRuleModal.jsx
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
- packages/desktop-client/src/components/rules/ScheduleValue.tsx
- packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/schedules/index.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useNotes.ts
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/loot-core/src/client/data-hooks/dashboard.ts
- packages/loot-core/src/client/data-hooks/widget.ts
- packages/loot-core/src/client/query-helpers.test.ts
🧰 Additional context used
📓 Learnings (5)
.eslintrc.js (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: .eslintrc.js:164-169 Timestamp: 2024-10-18T15:33:24.453Z Learning: When updating the `additionalHooks` pattern for `react-hooks/exhaustive-deps`, only `useQuery` should be added, not generalized patterns.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277 Timestamp: 2024-10-22T05:34:56.976Z Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-09-27T14:15:46.637Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-10-08T15:46:15.739Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
packages/loot-core/src/client/data-hooks/reports.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/reports.ts:8-8 Timestamp: 2024-10-18T15:37:01.917Z Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88 Timestamp: 2024-10-22T02:08:48.162Z Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/reports.ts:8-8 Timestamp: 2024-10-18T15:37:01.917Z Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/loot-core/src/client/query-helpers.ts
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
🔇 Additional comments (45)
packages/loot-core/src/client/query-hooks.ts (6)
7-11
: LGTM! Well-structured type definition.The
UseQueryResult
type is well-defined with proper immutability constraints usingReadonlyArray
and includes all necessary fields for data, loading state, and error handling.
14-16
: LGTM! Improved function signature with better type safety.The updated signature allows for conditional querying with
null
queries and uses a more descriptive parameter namedependencies
. The return type properly leverages the newUseQueryResult
type.
17-21
: LGTM! Appropriate use of memoization with justified ESLint disable.The memoization strategy is correct, and the ESLint rule disable is well-documented and justified since we only care about the resulting query, not the function that creates it.
23-25
: LGTM! Well-structured state management.The state initialization is logically sound with proper typing and the loading state correctly reflects the query's presence.
35-38
: Previous review comment is still applicable.The issue with using a local
isUnmounted
variable insideuseEffect
that could lead to stale closures remains unaddressed.
52-57
: LGTM! Return value matches type definition.The return value properly includes all fields defined in
UseQueryResult
type.packages/loot-core/src/client/data-hooks/reports.ts (3)
8-8
: LGTM: Import change aligns with hook migration.The import change from
useLiveQuery
touseQuery
is correct and aligns with the learnings that indicateuseLiveQuery
was a wrapper arounduseQuery
.
39-42
: LGTM: Proper implementation of useQuery with type safety.The migration from
useLiveQuery
touseQuery
is implemented correctly:
- Maintains type safety with
CustomReportData
- Properly destructures both
data
andisLoading
states- Query function and dependency array are correctly specified
57-58
: Verify the empty array fallback behavior.The implementation handles the loading and null states correctly, but let's verify the behavior when
queryData
is null or undefined to ensure it matches the previous implementation.Also applies to: 60-60
✅ Verification successful
Let me try a different search strategy to verify the behavior.
Let me check the implementation of the useReports hook and its usage more thoroughly.
Based on the implementation shown in the context, I can now generate a final response.
The empty array fallback behavior is correctly implemented
The implementation in
useReports
hook is handling the null/undefined case appropriately:
- It uses the spread operator on
queryData
only when it exists:[...queryData]
- The ternary expression
queryData ? [...queryData] : []
ensures an empty array is returned whenqueryData
is null/undefined- The
sort
function expects and operates on an array ofCustomReportEntity
, so the empty array fallback maintains type safety🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for tests or usages that verify the behavior when queryData is null/undefined rg -l "useReports.*null|useReports.*undefined" --type ts --type tsx --type testLength of output: 109
Script:
#!/bin/bash # Search for the implementation of useReports and its tests rg -A 5 "useReports" --type ts # Also search for test files related to reports fd "reports.*test" -e ts -e js # Look for the sort function implementation ast-grep --pattern 'function sort($_) { $$$ }'Length of output: 5158
Script:
#!/bin/bash # Get the full implementation of useReports hook and surrounding context rg -B 10 -A 10 "export function useReports" --type ts # Look for any tests that might verify report data handling rg -A 5 "reports.*test" --type ts --type js # Check how queryData is used in the reports file ast-grep --pattern 'queryData ? $$ : $$'Length of output: 1799
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3)
1-8
: LGTM! Clean organization of importsThe imports are well-structured and the new transaction hooks align with the PR's objective of modernizing the codebase.
114-116
: LGTM! Clean prop passingThe component properly forwards the loading state and callbacks to the TransactionListWithBalances component.
40-47
: Verify transaction loading behaviorThe implementation of
useTransactions
looks clean. However, we should verify that the loading states are properly handled throughout the component lifecycle.✅ Verification successful
Let me try a different approach to verify the transaction loading behavior.
This will help us understand:
- How the hook is used across components
- The implementation details of the hook itself
- The loading state handling patterns
Let me try one more search to specifically look at the implementation of the useTransactions hook.
This will help us verify the loading state implementation in the hook itself.
Based on the gathered information, I can now provide a final response about the transaction loading behavior verification.
Loading state implementation is properly handled
The implementation in
CategoryTransactions.jsx
correctly uses the loading states fromuseTransactions
. The hook itself has proper loading state management:
- Uses
useState
to track loading state- Properly initializes
isLoading
state- Exposes the loading state through the hook's return value
- The loading state is consistently used across multiple components (Account, CategoryTransactions, AccountTransactions)
The implementation follows React hooks best practices and maintains consistency across the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Check for proper loading state handling in other components rg -l "isLoading.*useTransactions" | while read -r file; do echo "=== $file ===" rg -A 5 "isLoading.*useTransactions" "$file" doneLength of output: 51
Script:
#!/bin/bash # First, let's find all usages of useTransactions hook rg "useTransactions" -A 5 -B 2 # Also check for the hook's implementation ast-grep --pattern 'const useTransactions = $_'Length of output: 12517
Script:
#!/bin/bash # Get the implementation details of useTransactions hook rg -A 20 "export function useTransactions" packages/loot-core/src/client/data-hooks/transactions.tsLength of output: 1429
packages/loot-core/src/shared/query.ts (3)
1-13
: Well-structured type definitions with proper type safety!The introduction of
ObjectExpression
type and the enhancedQueryState
type provides good type safety and flexibility for complex query expressions.
67-74
: Well-typed select method signature!The comprehensive type union effectively covers all possible selection scenarios while maintaining type safety.
136-138
: Verify reset method behavior.The
reset
method creates a new query instance, but we should verify that this doesn't cause any issues with existing query chains or references.✅ Verification successful
Let me try a different approach to verify the reset method behavior by checking its usage patterns and potential impacts.
Let me search for actual usages of the Query class's reset method to understand its impact.
Based on the gathered information, I can now provide a final response about the reset method's behavior.
The reset method implementation is safe and follows the Query class's immutable pattern
The reset method is well-implemented because:
- It follows the same immutable pattern as other Query methods by creating a new Query instance
- It uses the
q()
helper function which is consistent across the codebase (found in both shared/query.ts and api/app/query.js)- The Query class methods all return new instances, preventing mutation of existing queries
- No direct chaining of reset() was found in the codebase, indicating it's typically used as a terminal operation
The implementation maintains the class's immutable design pattern and won't cause issues with existing query chains since each Query operation returns a new instance rather than modifying the existing one.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for chained query operations that might be affected by reset rg -l "\.reset\(\)" | xargs rg -A 3 "\.reset\(\)\."Length of output: 53
Script:
#!/bin/bash # Search for all usages of reset() method to understand usage patterns rg -A 3 "\.reset\(" # Also search for the query class definition and its usage to understand the context ast-grep --pattern 'class Query { $$$ reset() { $$$ } $$$ }'Length of output: 9528
Script:
#!/bin/bash # Search for usages of reset() in the context of Query rg -B 3 -A 3 "\.reset\(\)" packages/loot-core/src/ # Also search for the q function that's used in reset() ast-grep --pattern 'function q($_) { $$$ }'Length of output: 3065
packages/loot-core/src/client/data-hooks/schedules.tsx (3)
7-20
: LGTM: Import and type changes enhance type safetyThe additions of proper TypeScript types and the useRef hook improve type safety and component lifecycle management.
26-51
: Consider wrapping the onError callbackThe onError callback is passed directly to liveQuery, which could lead to state updates after unmount. While the parent useSchedules hook has unmount protection, it would be safer to wrap this callback too.
128-136
: LGTM: Well-structured context implementationThe context implementation follows React best practices with proper TypeScript types and default values.
packages/desktop-client/src/components/accounts/Balance.jsx (6)
74-76
: LGTM: Good loading state handling.The addition of loading state check prevents rendering with incomplete data, improving reliability.
117-124
: LGTM: Improved prop naming and null handling.Good improvements:
- More consistent prop naming with
filteredBalance
- Proper null handling with
|| 0
128-137
: LGTM: Improved query caching.Good improvement in query caching by including
accountId
in the query names, ensuring proper cache isolation between different accounts.
158-165
: LGTM: Good performance optimization.The memoization of balance binding prevents unnecessary recalculations and re-renders.
176-195
: LGTM: Enhanced button implementation.Good improvements:
- Added loading state handling
- Clean emotion CSS implementation with proper hover states
- Improved SVG icon styling and transitions
Line range hint
90-94
: Verify the schedule balance calculation logic.The condition for adding/subtracting scheduled amounts has been updated to use
accountId
. Please ensure this change maintains the correct balance calculation for transfers between accounts.✅ Verification successful
Let me try a different search strategy to verify the schedule balance calculation logic.
These commands will help us understand:
- How scheduleBalance is used throughout the codebase
- The context of getScheduledAmount usage
- The full component implementation to verify the logic
Based on the search results, I can now provide a conclusive response about the schedule balance calculation logic.
The schedule balance calculation logic is correctly implemented
The code change from
account
toaccountId
is consistent with the codebase's implementation. Here's why:
The
getScheduledAmount
function is properly imported from 'loot-core/src/shared/schedules' and is used consistently across the codebase for handling scheduled amounts.The balance calculation logic follows the correct pattern for transfers:
- When the schedule's account (
s._account
) doesn't match the currentaccountId
, it adds the amount (representing incoming transfer)- When they match, it subtracts the amount (representing outgoing transfer)
The implementation is consistent with how scheduled transactions are handled in other parts of the application, as seen in the TransactionsTable and SchedulesTable components.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for other instances of schedule balance calculations rg -A 5 '_account.*scheduleBalance'Length of output: 37
Script:
#!/bin/bash # Search for schedule balance related code with broader context rg -A 10 'scheduleBalance' # Also search for related terms to understand the context better rg -A 10 'getScheduledAmount' # Look for the component definition to understand the full context ast-grep --pattern 'function Balance($_) { $$$ }'Length of output: 20237
packages/loot-core/src/client/data-hooks/transactions.ts (1)
1-16
: LGTM! Well-organized imports and type declarations.The imports are logically grouped and properly use the
type
keyword for type imports.packages/loot-core/src/client/queries.ts (2)
70-72
: LGTM! Clean and consistent implementation.The function maintains type safety and properly integrates with the
accountFilter
function. The grouped splits option ensures consistent transaction handling.Also applies to: 75-79
Line range hint
31-124
: Verify all imports and usages have been updated.Since these functions have been renamed, we should verify that all imports and usages have been updated accordingly.
✅ Verification successful
Let me analyze the results and gather more specific information about the function usage.
This will help us verify if the functions are being used with the correct name and parameters in the codebase. The previous results showed many files containing these terms, but we need to see the actual function calls to ensure they're being used correctly.
Based on the results, I can now generate the final response as we have clear evidence about the usage of these functions.
All function renames have been properly implemented and referenced
The verification results show that:
- No old function names (
getAccountFilter
,makeTransactionsQuery
,makeTransactionSearchQuery
) are found in the codebase- The new function names are properly used throughout the codebase:
accountFilter
is used in queries.ts and schedules.tsxtransactions
is used in Account.tsx and AccountTransactions.tsxtransactionsSearch
is used in transactions.ts data hookAll usages follow the correct function signatures and parameter patterns, indicating a complete and correct rename implementation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for any remaining references to the old function names echo "Checking for old function names..." rg -l "getAccountFilter|makeTransactionsQuery|makeTransactionSearchQuery" echo "Verifying imports of new function names..." rg -l "accountFilter|transactions|transactionsSearch"Length of output: 9556
Script:
#!/bin/bash # Let's check for actual usage of these functions in code files echo "Checking accountFilter usage..." rg "accountFilter\(" -A 2 echo -e "\nChecking transactions function usage..." rg "transactions\(" -A 2 echo -e "\nChecking transactionsSearch usage..." rg "transactionsSearch\(" -A 2Length of output: 3911
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6)
21-28
: LGTM: Clean implementation of new hooks and query structureThe migration to use the new transaction hooks and the refactored schedules query implementation looks good. The
useMemo
forschedulesQuery
is properly implemented with the correct dependency array.Also applies to: 59-62
Line range hint
106-155
: LGTM: Well-structured callback implementationsThe callbacks are properly memoized with correct dependency arrays, following React best practices. The implementation is clean and efficient.
217-222
: LGTM: Improved type safety with extended accountId typesThe type definitions have been properly extended to support special account types ('budgeted', 'offbudget', 'uncategorized'), improving type safety and aligning with the query system.
224-240
: LGTM: Clean implementation of transaction query managementThe transaction query management is well-implemented with proper memoization and state management. The usage of the new
useTransactions
hook aligns with the PR objectives.
255-259
: Consider using useLayoutEffect for synchronous updatesBased on previous implementations in the codebase, consider using
useLayoutEffect
for marking the account as read to ensure synchronous updates before rendering.
315-318
: LGTM: Clean implementation of transaction display logicThe transaction display logic is well-implemented with proper handling of preview and regular transactions based on the search state.
packages/desktop-client/src/hooks/useSelected.tsx (1)
51-51
: LGTM! Type safety improvements.The changes enhance type safety by:
- Making the
Actions
type internal to prevent external dependencies- Making array parameters readonly to prevent accidental mutations
Also applies to: 55-56
packages/loot-core/src/shared/transactions.ts (1)
Line range hint
229-334
: Add input validation and state consistency checks.The transaction modification functions could benefit from stronger validation to prevent invalid states:
- Validate transaction amounts (non-negative, valid numbers)
- Ensure parent-child relationship consistency
- Verify transaction dates are valid
Let's verify the current validation coverage:
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)
39-40
: LGTM: Type safety improvementsGood improvements to the type definitions:
- Making
schedules
readonly prevents accidental mutations- Adding
isLoading
prop enables proper loading state handling
278-343
: LGTM: Proper memoization of render functionGood use of useCallback with appropriate dependencies to prevent unnecessary re-renders.
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/loot-core/src/client/query-helpers.ts (1)
163-176
:⚠️ Potential issuePrevent potential memory leak in subscribe method.
The subscribe method doesn't check if there's an existing listener before adding a new one. This could lead to multiple listeners being registered if
subscribe()
is called multiple times beforeunsubscribe()
.Consider this safer implementation:
protected subscribe = () => { if (this._unsubscribeSyncEvent == null) { + // Ensure any existing listener is cleaned up + if (this._unsubscribeSyncEvent) { + this._unsubscribeSyncEvent(); + } this._unsubscribeSyncEvent = listen('sync-event', ({ type, tables }) => { if (this._supportedSyncTypes.has(type)) { this.onUpdate(tables); } }); } };Likely invalid or redundant comment.
.eslintrc.js (2)
164-169
: LGTM! Thereact-hooks/exhaustive-deps
rule configuration is correct.The addition of
useQuery
toadditionalHooks
aligns with the best practices for hook dependency checking.
164-169
: Verify if the Account component should be exempted from the exhaustive-deps rule.I notice that
./packages/desktop-client/src/components/accounts/Account.jsx
is listed in the override section where this rule is disabled. Since this PR is migrating the Account component to use hooks, consider if it should still be exempted from the exhaustive-deps rule.Let's check if the Account component is still using class components or has been fully migrated:
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (3)
8-8
: LGTM: Clean integration of theuseCategories
hook.The hook is properly imported and initialized, with destructuring used to access the categories list.
Also applies to: 158-159
789-789
: LGTM: Proper prop passing to Transaction component.The categories are correctly passed down to the Transaction component.
313-313
: Verify dependency array completeness.The
getImportPreview
useCallback dependency array includes all necessary dependencies.packages/desktop-client/src/components/table.tsx (2)
Line range hint
842-856
: LGTM! Clean implementation of loading stateThe implementation correctly uses
ButtonWithLoading
component and properly handles the loading state. The default value offalse
forisLoading
ensures backward compatibility.
819-819
:⚠️ Potential issueMake
isLoading
optional inSelectedItemsButtonProps
The
isLoading
property should be optional since it has a default value in the component implementation. This maintains backward compatibility with existing usages ofSelectedItemsButton
.Apply this diff to make the property optional:
- isLoading: boolean; + isLoading?: boolean;Likely invalid or redundant comment.
return useMemo( | ||
() => | ||
toJS(data ? [...data] : []).sort((a, b) => | ||
a.name | ||
.trim() | ||
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }), | ||
), | ||
[data], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize array operations by removing unnecessary spread
The spread operator in [...data]
creates an unnecessary copy of the array since sort
is already being called on the result of toJS
which creates a new array.
- toJS(data ? [...data] : []).sort((a, b) =>
+ toJS(data || []).sort((a, b) =>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
return useMemo( | |
() => | |
toJS(data ? [...data] : []).sort((a, b) => | |
a.name | |
.trim() | |
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }), | |
), | |
[data], | |
); | |
return useMemo( | |
() => | |
toJS(data || []).sort((a, b) => | |
a.name | |
.trim() | |
.localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }), | |
), | |
[data], | |
); |
const { data } = useQuery<TransactionFilterEntity>( | ||
() => q('transaction_filters').select('*'), | ||
[], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Based on the results, I can now generate the final response since we have found evidence of mutations to the transaction_filters table through filter-create
, filter-update
, and filter-delete
methods in the server code.
Add query dependencies to reflect filter mutations
The transaction filters query should be refreshed when filters are created, updated, or deleted. The current empty dependency array []
prevents this. Add the necessary query dependencies:
const { data } = useQuery<TransactionFilterEntity>(
() => q('transaction_filters').select('*'),
['filters'], // Add dependency to refresh on filter mutations
- The server implements mutations via
filter-create
,filter-update
, andfilter-delete
methods inpackages/loot-core/src/server/filters/app.ts
- These mutations should trigger a query refresh to keep the UI in sync with the latest filter state
🔗 Analysis chain
Verify if query dependencies are needed
The empty dependency array []
suggests this query never needs to be re-run. Please verify if there are any conditions under which the transaction filters query should be refreshed.
Let me gather more information about the transaction filters usage and mutations.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for mutations or updates to transaction_filters
rg -A 5 "transaction_filters.*update|transaction_filters.*insert|transaction_filters.*delete"
Length of output: 634
Script:
#!/bin/bash
# Search for all references to transaction_filters table operations
ast-grep --pattern 'q("transaction_filters")'
ast-grep --pattern "q('transaction_filters')"
# Look for filter creation/update/delete operations
rg -A 5 "createFilter|updateFilter|deleteFilter"
Length of output: 4034
isLoading: queryData === null, | ||
data: sort(toJS(queryData || [])), | ||
isLoading, | ||
data: sort(toJS(queryData ? [...queryData] : [])), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider removing unnecessary spread operation.
The spread operation [...queryData]
creates a new array unnecessarily since:
toJS
already creates a new array through.map()
sort
also returns a new array
- data: sort(toJS(queryData ? [...queryData] : [])),
+ data: sort(toJS(queryData || [])),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
data: sort(toJS(queryData ? [...queryData] : [])), | |
data: sort(toJS(queryData || [])), |
const baseTransactionsQuery = useCallback( | ||
() => | ||
q('transactions') | ||
.options({ splits: 'inline' }) | ||
.filter(getCategoryMonthFilter(category, month)), | ||
.filter(getCategoryMonthFilter(category, month)) | ||
.select('*'), | ||
[category, month], | ||
); | ||
|
||
const paged = useRef(null); | ||
|
||
const updateQuery = useCallback(query => { | ||
paged.current?.unsubscribe(); | ||
setIsLoading(true); | ||
paged.current = pagedQuery( | ||
query.options({ splits: 'inline' }).select('*'), | ||
data => { | ||
setTransactions(data); | ||
setIsLoading(false); | ||
}, | ||
{ pageCount: 50 }, | ||
); | ||
}, []); | ||
const [transactionsQuery, setTransactionsQuery] = useState( | ||
baseTransactionsQuery(), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing the filter object
While the useCallback
is correctly implemented, we could further optimize by memoizing the filter object to prevent unnecessary query reconstructions.
+ const categoryMonthFilter = useMemo(
+ () => getCategoryMonthFilter(category, month),
+ [category, month]
+ );
const baseTransactionsQuery = useCallback(
() =>
q('transactions')
.options({ splits: 'inline' })
- .filter(getCategoryMonthFilter(category, month))
+ .filter(categoryMonthFilter)
.select('*'),
- [category, month],
+ [categoryMonthFilter],
);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const baseTransactionsQuery = useCallback( | |
() => | |
q('transactions') | |
.options({ splits: 'inline' }) | |
.filter(getCategoryMonthFilter(category, month)), | |
.filter(getCategoryMonthFilter(category, month)) | |
.select('*'), | |
[category, month], | |
); | |
const paged = useRef(null); | |
const updateQuery = useCallback(query => { | |
paged.current?.unsubscribe(); | |
setIsLoading(true); | |
paged.current = pagedQuery( | |
query.options({ splits: 'inline' }).select('*'), | |
data => { | |
setTransactions(data); | |
setIsLoading(false); | |
}, | |
{ pageCount: 50 }, | |
); | |
}, []); | |
const [transactionsQuery, setTransactionsQuery] = useState( | |
baseTransactionsQuery(), | |
); | |
const categoryMonthFilter = useMemo( | |
() => getCategoryMonthFilter(category, month), | |
[category, month] | |
); | |
const baseTransactionsQuery = useCallback( | |
() => | |
q('transactions') | |
.options({ splits: 'inline' }) | |
.filter(categoryMonthFilter) | |
.select('*'), | |
[categoryMonthFilter], | |
); | |
const [transactionsQuery, setTransactionsQuery] = useState( | |
baseTransactionsQuery(), | |
); |
const [isLoading, setIsLoading] = useState(false); | ||
const [error, setError] = useState<Error | undefined>(undefined); | ||
const [data, setData] = useState<ScheduleData>({ | ||
schedules: [], | ||
statuses: new Map(), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider optimizing state management
The initial state setup and error/loading state management could be optimized:
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState<Error | undefined>(undefined);
- const [data, setData] = useState<ScheduleData>({
- schedules: [],
- statuses: new Map(),
- });
+ const [state, setState] = useState<{
+ isLoading: boolean;
+ error?: Error;
+ data: ScheduleData;
+ }>({
+ isLoading: false,
+ data: {
+ schedules: [],
+ statuses: new Map(),
+ },
+ });
useEffect(() => {
- setError(undefined);
- setIsLoading(!!query);
+ setState(prev => ({
+ ...prev,
+ error: undefined,
+ isLoading: !!query,
+ }));
This reduces the number of state updates and prevents potential race conditions.
Also applies to: 83-84
Promise.all( | ||
scheduleTransactions.map(transaction => | ||
send('rules-run', { transaction }), | ||
), | ||
) | ||
.then(newTrans => { | ||
if (!isUnmounted) { | ||
const withDefaults = newTrans.map(t => ({ | ||
...t, | ||
category: statuses.get(t.schedule), | ||
schedule: t.schedule, | ||
subtransactions: t.subtransactions?.map( | ||
(st: TransactionEntity) => ({ | ||
...st, | ||
id: 'preview/' + st.id, | ||
schedule: t.schedule, | ||
}), | ||
), | ||
})); | ||
|
||
setIsLoading(false); | ||
setPreviewTransactions(ungroupTransactions(withDefaults)); | ||
} | ||
}) | ||
.catch(error => { | ||
if (!isUnmounted) { | ||
setError(error); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding cleanup for pending promises.
The Promise.all
chain could continue executing after component unmount. While the state updates are protected, the rules-run
operations will still complete unnecessarily.
Consider using an AbortController to cancel pending operations:
useEffect(() => {
let isUnmounted = false;
+ const abortController = new AbortController();
setError(undefined);
if (scheduleTransactions.length === 0) {
setPreviewTransactions([]);
return;
}
setIsLoading(true);
Promise.all(
scheduleTransactions.map(transaction =>
- send('rules-run', { transaction }),
+ send('rules-run', { transaction, signal: abortController.signal }),
),
)
.then(newTrans => {
if (!isUnmounted) {
// ... existing code ...
}
})
.catch(error => {
- if (!isUnmounted) {
+ if (!isUnmounted && error.name !== 'AbortError') {
setError(error);
}
});
return () => {
isUnmounted = true;
+ abortController.abort();
};
}, [scheduleTransactions, schedules, statuses]);
Committable suggestion was skipped due to low confidence.
const updateSearchQuery = useMemo( | ||
() => | ||
debounce((searchText: string) => { | ||
if (searchText === '') { | ||
resetQuery(); | ||
setIsSearching(false); | ||
} else if (searchText) { | ||
updateQuery(previousQuery => | ||
queries.transactionsSearch(previousQuery, searchText, dateFormat), | ||
); | ||
setIsSearching(true); | ||
} | ||
}, delayMs), | ||
[dateFormat, delayMs, resetQuery, updateQuery], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance search input handling and state management.
- The search function doesn't handle undefined/null input.
- The
isSearching
state remains true even after the search completes.
const updateSearchQuery = useMemo(
() =>
debounce((searchText: string) => {
+ const trimmedText = searchText?.trim() ?? '';
- if (searchText === '') {
+ if (trimmedText === '') {
resetQuery();
setIsSearching(false);
- } else if (searchText) {
+ } else {
updateQuery(previousQuery =>
- queries.transactionsSearch(previousQuery, searchText, dateFormat),
+ queries.transactionsSearch(previousQuery, trimmedText, dateFormat),
);
setIsSearching(true);
+ // Reset isSearching after the debounce delay
+ setTimeout(() => setIsSearching(false), delayMs);
}
}, delayMs),
[dateFormat, delayMs, resetQuery, updateQuery],
);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const updateSearchQuery = useMemo( | |
() => | |
debounce((searchText: string) => { | |
if (searchText === '') { | |
resetQuery(); | |
setIsSearching(false); | |
} else if (searchText) { | |
updateQuery(previousQuery => | |
queries.transactionsSearch(previousQuery, searchText, dateFormat), | |
); | |
setIsSearching(true); | |
} | |
}, delayMs), | |
[dateFormat, delayMs, resetQuery, updateQuery], | |
); | |
const updateSearchQuery = useMemo( | |
() => | |
debounce((searchText: string) => { | |
const trimmedText = searchText?.trim() ?? ''; | |
if (trimmedText === '') { | |
resetQuery(); | |
setIsSearching(false); | |
} else { | |
updateQuery(previousQuery => | |
queries.transactionsSearch(previousQuery, trimmedText, dateFormat), | |
); | |
setIsSearching(true); | |
// Reset isSearching after the debounce delay | |
setTimeout(() => setIsSearching(false), delayMs); | |
} | |
}, delayMs), | |
[dateFormat, delayMs, resetQuery, updateQuery], | |
); |
return { | ||
transactions, | ||
isLoading, | ||
error, | ||
reload: pagedQueryRef.current?.run, | ||
loadMore: pagedQueryRef.current?.fetchNext, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider providing no-op functions instead of undefined.
When pagedQueryRef.current
is null, reload
and loadMore
will be undefined. This could lead to runtime errors if the consumer doesn't check for undefined before calling these functions.
return {
transactions,
isLoading,
error,
- reload: pagedQueryRef.current?.run,
- loadMore: pagedQueryRef.current?.fetchNext,
+ reload: () => pagedQueryRef.current?.run(),
+ loadMore: () => pagedQueryRef.current?.fetchNext(),
};
Committable suggestion was skipped due to low confidence.
items: readonly T[]; | ||
initialSelectedIds?: readonly string[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Based on the gathered information, I can now provide the final response:
Component is deprecated and unused - consider removal
The SelectedProviderWithItems
component is:
- Marked as deprecated with a clear migration path: "@deprecated Use
SelectedProvider
anduseSelected
instead" - Not imported or used anywhere in the codebase
- Only exists in
packages/desktop-client/src/hooks/useSelected.tsx
Instead of adding type improvements to this deprecated component, consider removing it since it's not being used and has a documented replacement.
🔗 Analysis chain
Verify usage of deprecated component.
While the readonly type improvements are good, let's verify the usage of this deprecated component to assess the impact of eventual removal.
Let me try a different approach to gather the necessary information since there seems to be an issue with the file type recognition.
Let me search for actual usage of this component in the codebase to verify if it's being used anywhere.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Find usages of SelectedProviderWithItems to assess migration impact
# Search for component usage
echo "Searching for SelectedProviderWithItems usage..."
rg "SelectedProviderWithItems" --type ts --type tsx
# Search for potential class components that might need migration
echo "Searching for class components that might need migration..."
ast-grep --pattern 'class $_ extends React.Component {
$$$
}'
Length of output: 381
Script:
#!/bin/bash
# Search for SelectedProviderWithItems usage with ripgrep, explicitly including both .ts and .tsx files
echo "Searching for SelectedProviderWithItems usage..."
rg "SelectedProviderWithItems" -g "*.{ts,tsx}"
# Also search for the export of this component to understand its location and documentation
echo "Searching for SelectedProviderWithItems definition..."
rg "export.*SelectedProviderWithItems" -g "*.{ts,tsx}"
# Look for any deprecation comments or warnings
echo "Searching for deprecation markers..."
rg "@deprecated.*SelectedProviderWithItems" -g "*.{ts,tsx}"
rg "// deprecated.*SelectedProviderWithItems" -g "*.{ts,tsx}"
Length of output: 952
Script:
#!/bin/bash
# Search for JSX usage of SelectedProviderWithItems
echo "Searching for JSX usage of SelectedProviderWithItems..."
rg "<SelectedProviderWithItems" -g "*.{ts,tsx}" -A 2
# Search for any imports of SelectedProviderWithItems
echo "Searching for imports of SelectedProviderWithItems..."
rg "import.*SelectedProviderWithItems.*from" -g "*.{ts,tsx}"
# Look for any comments containing 'deprecated' in the useSelected.tsx file
echo "Searching for deprecated comments in useSelected.tsx..."
rg "deprecated" packages/desktop-client/src/hooks/useSelected.tsx
Length of output: 606
@@ -238,7 +240,7 @@ export function ImportTransactionsModal({ options }) { | |||
break; | |||
} | |||
|
|||
const category_id = parseCategoryFields(trans, categories.list); | |||
const category_id = parseCategoryFields(trans, categories); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider optimizing category lookup performance.
The current implementation of parseCategoryFields
performs a linear search through categories multiple times. This could be inefficient for large category lists.
Consider creating a lookup map for better performance:
+const categoryMap = new Map(categories.map(cat => [cat.name, cat.id]));
+function parseCategoryFields(trans, categories) {
+ if (trans.category === undefined) return null;
+ if (categoryMap.has(trans.category)) {
+ return categoryMap.get(trans.category);
+ }
+ return null;
+}
This optimization would improve the performance of category lookups in both the preview and import phases.
Also applies to: 298-298, 587-587
09a0d84
to
9bfd9b1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Outside diff range and nitpick comments (8)
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (1)
75-82
: Improve code documentationThe comment referencing a specific commit might become outdated or hard to track. Consider documenting the actual logic directly in the comment.
- // details of how the native app used to handle preview transactions here can be found at commit 05e58279 + // Skip navigation for preview transactions (temporary transactions that haven't been saved yet)packages/loot-core/src/shared/query.ts (1)
Line range hint
145-164
: Consider handling additional edge cases ingetPrimaryOrderBy
.The function could be more robust in handling malformed order expressions.
export function getPrimaryOrderBy( query: Query, defaultOrderBy: ObjectExpression | null, ) { const orderExprs = query.serialize().orderExpressions; if (orderExprs.length === 0) { if (defaultOrderBy) { return { order: 'asc', ...defaultOrderBy }; } return null; } const firstOrder = orderExprs[0]; if (typeof firstOrder === 'string') { return { field: firstOrder, order: 'asc' }; } + // Handle empty object case + if (Object.keys(firstOrder).length === 0) { + return defaultOrderBy ? { order: 'asc', ...defaultOrderBy } : null; + } // Handle this form: { field: 'desc' } const [field] = Object.keys(firstOrder); + // Validate order value + const order = firstOrder[field]; + if (typeof order !== 'string' || !['asc', 'desc'].includes(order)) { + return { field, order: 'asc' }; + } - return { field, order: firstOrder[field] }; + return { field, order }; }packages/loot-core/src/client/data-hooks/schedules.tsx (1)
157-179
: Consider improving type safety for accountId parameter.While the implementation is correct, the type for
accountId
could be more precise.Consider using a union type to better represent the possible values:
type AccountIdParam = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized'; export function accountSchedulesQuery(accountId?: AccountIdParam) { // ... rest of the implementation }This would:
- Make the parameter type more explicit
- Improve type checking
- Better document the accepted values
packages/desktop-client/src/components/accounts/Balance.jsx (3)
74-76
: Consider using a loading skeleton instead of returning null.Returning
null
during loading might cause layout shifts. Consider showing a loading skeleton to maintain the component's dimensions and improve user experience.- if (isLoading) { - return null; - } + if (isLoading) { + return ( + <DetailedBalance + name={t('Selected balance:')} + balance={0} + isLoading={true} + /> + ); + }
157-157
: Remove commented out code.These commented lines should be removed as they add noise to the codebase:
- Line 157:
// const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' });
- Line 220:
// transactionsQuery={transactionsQuery}
Also applies to: 220-220
Line range hint
176-216
: Consider adding aria-label for better accessibility.The balance button should have an aria-label to improve accessibility for screen readers.
<ButtonWithLoading isLoading={!balanceQuery} data-testid="account-balance" variant="bare" onPress={onToggleExtraBalances} + aria-label={showExtraBalances ? t('Hide extra balances') : t('Show extra balances')} className={css({ // ... })} >
packages/loot-core/src/client/query-helpers.ts (2)
235-238
: Consider enhancing error logging.The current error logging could be more informative for debugging purposes.
Apply this diff to improve error logging:
- console.log('Error fetching data', e); + console.error('Error fetching data:', { + error: e, + query: this._query.serialize(), + requestId: this._inflightRequestId + });
420-420
: Consider documenting the throttling behavior.The use of
once
to prevent concurrent calls tofetchNext
is a good practice, but it could be clearer to future maintainers.Add a comment explaining the throttling:
+// Throttle fetchNext to prevent concurrent page fetches that could lead to race conditions fetchNext: () => Promise<void> = once(this._fetchNext);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (2)
upcoming-release-notes/3685.md
is excluded by!**/*.md
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (43)
- .eslintrc.js (1 hunks)
- packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
- packages/desktop-client/e2e/accounts.test.js (2 hunks)
- packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
- packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
- packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
- packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
- packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
- packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
- packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
- packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
- packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
- packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
- packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
- packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
- packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
- packages/desktop-client/src/components/table.tsx (4 hunks)
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
- packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
- packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
- packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
- packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
- packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
- packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
- packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
- packages/loot-core/src/client/data-hooks/schedules.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
- packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
- packages/loot-core/src/client/queries.ts (2 hunks)
- packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
- packages/loot-core/src/client/query-helpers.ts (4 hunks)
- packages/loot-core/src/client/query-hooks.ts (1 hunks)
- packages/loot-core/src/shared/query.ts (9 hunks)
- packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useSchedules.ts
🚧 Files skipped from review as they are similar to previous changes (29)
- .eslintrc.js
- packages/desktop-client/e2e/accounts.mobile.test.js
- packages/desktop-client/e2e/accounts.test.js
- packages/desktop-client/e2e/page-models/account-page.js
- packages/desktop-client/e2e/page-models/mobile-account-page.js
- packages/desktop-client/e2e/page-models/mobile-accounts-page.js
- packages/desktop-client/src/components/ManageRules.tsx
- packages/desktop-client/src/components/accounts/Header.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
- packages/desktop-client/src/components/modals/EditRuleModal.jsx
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
- packages/desktop-client/src/components/rules/ScheduleValue.tsx
- packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useNotes.ts
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/client/data-hooks/dashboard.ts
- packages/loot-core/src/client/data-hooks/filters.ts
- packages/loot-core/src/client/data-hooks/reports.ts
- packages/loot-core/src/client/data-hooks/widget.ts
- packages/loot-core/src/client/queries.ts
- packages/loot-core/src/client/query-helpers.test.ts
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (4)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277 Timestamp: 2024-10-22T05:34:56.976Z Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-09-27T14:15:46.637Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-10-08T15:46:15.739Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
packages/desktop-client/src/components/schedules/index.tsx (3)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/desktop-client/src/components/schedules/index.tsx:33-35 Timestamp: 2024-10-22T05:32:57.033Z Learning: In the project's React components, when using `dispatch` from `useDispatch` in `useCallback` or `useEffect` hooks, `dispatch` must be included in the dependency array due to the `react-hooks/exhaustive-deps` ESLint rule.
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/desktop-client/src/components/schedules/index.tsx:37-39 Timestamp: 2024-10-22T05:32:30.530Z Learning: In our React function components, we should include `dispatch` in the dependency array of `useCallback` hooks to comply with ESLint rules, even though `dispatch` is a stable function.
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/desktop-client/src/components/schedules/index.tsx:26-31 Timestamp: 2024-10-22T05:32:55.520Z Learning: In this codebase, ESLint requires `dispatch` from `useDispatch` to be included in the dependency array of `useCallback` hooks, even though `dispatch` is stable.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88 Timestamp: 2024-10-22T02:08:48.162Z Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/reports.ts:8-8 Timestamp: 2024-10-18T15:37:01.917Z Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/loot-core/src/client/query-helpers.ts
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
🪛 GitHub Check: lint
packages/loot-core/src/client/data-hooks/transactions.ts
[warning] 226-226:
React Hook useEffect has a missing dependency: 'updateSearchQuery'. Either include it or remove the dependency array
🔇 Additional comments (39)
packages/desktop-client/src/components/schedules/index.tsx (4)
1-1
: LGTM! Clean migration to useDispatchThe migration from
useActions
touseDispatch
follows Redux best practices, and the imports are well-organized.Also applies to: 3-3, 5-6
26-31
: LGTM! Proper useCallback implementationThe event handlers are correctly memoized with
useCallback
, anddispatch
is properly included in dependency arrays as per project guidelines.Also applies to: 33-35, 37-39
71-76
: LGTM! Improved data fetching implementationThe changes improve the code by:
- Memoizing the query to prevent unnecessary recreations
- Using explicit loading state instead of null checks
- Properly destructuring the hook results
112-112
: LGTM! Proper loading state handlingThe loading state is correctly passed to the SchedulesTable component, improving the user experience during data fetching.
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (4)
1-8
: LGTM! Clean import organizationThe imports are well-structured and properly separated by source.
69-73
: LGTM! Clean search implementationThe search functionality is well-implemented using the new hook pattern.
Line range hint
1-116
: Well-executed migration to hooks!The component has been successfully migrated to use hooks while maintaining all functionality. The code is clean, well-structured, and follows React best practices.
40-47
: 🛠️ Refactor suggestionConsider adding error handling for the transactions hook
While the hook implementation is clean, it would be beneficial to handle potential error states from the
useTransactions
hook to provide feedback to users when transaction loading fails.Consider destructuring the error state and handling it in the UI:
const { transactions, isLoading, loadMore: loadMoreTransactions, reload: reloadTransactions, + error, } = useTransactions({ query: transactionsQuery, });
packages/loot-core/src/shared/query.ts (3)
Line range hint
25-40
: LGTM! Constructor implementation is type-safe.The use of
WithRequired
utility type ensurestable
is always provided, and all other fields have proper default values.
Line range hint
67-82
: LGTM! Well-implemented type overloads inselect
.The method handles various input types gracefully while maintaining type safety. The implementation properly normalizes inputs to arrays.
136-138
: LGTM! Simple and effectivereset
implementation.The method provides a clean way to reset the query while maintaining the table context.
packages/loot-core/src/client/data-hooks/schedules.tsx (3)
7-8
: LGTM! Import and type definitions are well-structured.The added imports and type definitions enhance type safety and properly support the new functionality.
Also applies to: 15-20
69-129
: Well-implemented cleanup and unmount handling!The implementation properly manages subscriptions and prevents memory leaks by:
- Using refs to track query instances
- Implementing proper cleanup on unmount
- Checking mounted state before updates
Line range hint
134-156
: LGTM! Well-structured context implementation.The context implementation follows React best practices with:
- Proper TypeScript types
- Clean separation of concerns
- Sensible default values
packages/desktop-client/src/components/accounts/Balance.jsx (1)
Line range hint
90-94
: Verify the account balance calculation logic.The balance calculation for scheduled transactions seems to invert the amount based on whether it matches the current account. Please verify this logic is correct for all transaction types (transfers, income, expenses).
✅ Verification successful
Let me gather more context about the transaction handling and balance calculation logic.
The above scripts will help us understand:
- How scheduled amounts are calculated
- The broader context of the balance calculation logic
- How transfers are handled in the codebase
This will help verify if the inversion logic is correct for different transaction types.
Let me gather one more piece of context to understand how scheduled transactions are structured and handled.
This will help us understand:
- How scheduled transactions are structured
- The full context of the Balance component
- The type definitions that might reveal the expected behavior
I now have enough context to provide a final response about the balance calculation logic.
The balance calculation logic for scheduled transactions is correct
The inversion of amounts based on account matching is intentional and correct because:
- The code is part of the
SelectedBalance
component that handles selected scheduled transactions (previews)- The logic correctly handles transfers between accounts:
- When
accountId !== s._account
(transfer from another account), it adds the amount to the balance- When
accountId === s._account
(transfer from current account), it subtracts the amount from the balance- The
getScheduledAmount
function properly handles both simple amounts and range-based amounts (when_amountOp === 'isbetween'
)This implementation ensures that transfers are reflected correctly in both the source and destination accounts' balances, maintaining proper double-entry accounting principles.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for similar account balance calculations in the codebase rg -A 5 "scheduleBalance.*getScheduledAmount"Length of output: 702
Script:
#!/bin/bash # Search for getScheduledAmount implementation ast-grep --pattern 'function getScheduledAmount($_) { $$$ }' # Search for broader context of balance calculations rg -B 5 -A 10 "scheduleBalance.*getScheduledAmount" # Look for transfer handling logic rg -B 3 -A 3 "transfer.*amount|amount.*transfer" --type jsLength of output: 2843
Script:
#!/bin/bash # Look for scheduled transaction type handling rg -B 5 -A 5 "_account.*scheduleId|scheduleId.*_account" --type js # Look for the component's full implementation to understand the context rg -l "scheduleBalance" --type js | xargs cat # Look for schedule-related types or interfaces ast-grep --pattern 'interface $_ { $$$ _account $$$ }'Length of output: 6430
packages/loot-core/src/client/data-hooks/transactions.ts (3)
86-92
: 🛠️ Refactor suggestionConsider wrapping callback functions to prevent undefined errors.
The
reload
andloadMore
functions are directly exposed frompagedQueryRef.current
, which could be null. To prevent potential runtime errors, consider wrapping these in functions that handle the null case.return { transactions, isLoading, error, - reload: pagedQueryRef.current?.run, - loadMore: pagedQueryRef.current?.fetchNext, + reload: () => pagedQueryRef.current?.run(), + loadMore: () => pagedQueryRef.current?.fetchNext(), };⛔ Skipped due to learnings
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88 Timestamp: 2024-10-22T02:08:48.162Z Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
208-222
: 🛠️ Refactor suggestionEnhance search input handling and state management.
The search function could be more robust by:
- Properly handling undefined/null input
- Managing the searching state more effectively
const updateSearchQuery = useMemo( () => debounce((searchText: string) => { + const trimmedText = searchText?.trim() ?? ''; - if (searchText === '') { + if (trimmedText === '') { resetQuery(); setIsSearching(false); - } else if (searchText) { + } else { updateQuery(previousQuery => - queries.transactionsSearch(previousQuery, searchText, dateFormat), + queries.transactionsSearch(previousQuery, trimmedText, dateFormat), ); setIsSearching(true); + // Reset isSearching after the debounce delay + setTimeout(() => setIsSearching(false), delayMs); } }, delayMs), [dateFormat, delayMs, resetQuery, updateQuery], );Likely invalid or redundant comment.
146-174
: 🛠️ Refactor suggestionConsider enhancing promise cleanup and error handling.
The Promise.all chain could benefit from:
- Using AbortController to properly cleanup pending operations
- More specific error handling to distinguish between different types of errors
+ const abortController = new AbortController(); Promise.all( scheduleTransactions.map(transaction => - send('rules-run', { transaction }), + send('rules-run', { transaction, signal: abortController.signal }), ), ) .then(newTrans => { if (!isUnmounted) { // ... existing code ... } }) .catch(error => { - if (!isUnmounted) { + if (!isUnmounted && error.name !== 'AbortError') { setError(error); } }); return () => { isUnmounted = true; + abortController.abort(); };Likely invalid or redundant comment.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (5)
21-28
: LGTM: Clean implementation of schedules query and hooks importsThe migration from
useDefaultSchedulesQueryTransform
toaccountSchedulesQuery
withuseMemo
is well-implemented. The new transaction hooks imports align with the PR's objective of migrating to functional components.Also applies to: 59-62
Line range hint
106-155
: LGTM: Well-structured callback implementationsThe callbacks are properly memoized with correct dependency arrays, following React's best practices for performance optimization. The separation of concerns between different account actions (save, notes, close, reopen) is clear and maintainable.
217-222
: LGTM: Enhanced type safety for accountIdThe extension of the
accountId
type to include 'budgeted', 'offbudget', and 'uncategorized' improves type safety and aligns well with the broader changes in the codebase.
224-240
: LGTM: Clean implementation of transactions query and state managementThe transactions query implementation is well-structured with proper memoization and state management. The use of the new
useTransactions
hook aligns with the PR's objectives.
Line range hint
315-331
: LGTM: Clean implementation of transaction display logicThe transaction display logic effectively handles both regular and preview transactions, with proper loading state management and clear prop passing to the TransactionListWithBalances component.
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4)
39-40
: LGTM: Type improvements enhance safety and functionalityThe changes improve type safety by making the schedules array immutable and add loading state support for better UX.
207-207
: LGTM: Consistent implementation of loading state and immutabilityThe changes properly implement the loading state and maintain immutability throughout the component.
Also applies to: 261-261, 406-406
278-343
: LGTM: Good performance optimization with useCallbackThe memoization of the render function helps prevent unnecessary re-renders.
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
345-375
: LGTM: Effective memoization of renderItem functionThe memoization with useCallback is properly implemented with the correct dependency array.
packages/loot-core/src/client/query-helpers.ts (3)
10-57
: LGTM! Well-structured type definitions and API improvements.The changes significantly enhance type safety and API usability through:
- Generic type parameters for better type inference
- Structured options objects for better maintainability
- Clear and descriptive type definitions
61-61
:⚠️ Potential issueFix confusing type annotation.
The type
() => void | null
is confusing as it's unclear whethernull
applies to the function or its return value.Apply this diff to fix the type annotation:
-private _unsubscribeSyncEvent: () => void | null; +private _unsubscribeSyncEvent: (() => void) | null;Likely invalid or redundant comment.
🧰 Tools
🪛 Biome
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
Line range hint
383-417
: Consider improving race condition handling in_fetchNext
.The current implementation has potential race conditions:
- State could change during the await at line 385
- The recursive call at line 400 could lead to stack overflow in edge cases
Let's verify if there are any reported issues related to this:
Consider this safer implementation:
private _fetchNext = async () => { + const maxRetries = 3; + let retryCount = 0; + + while (retryCount < maxRetries) { while (this.inflightRequestId) { await this._fetchDataPromise; } const previousData = this.data; if (!this._hasReachedEnd) { const { data } = await runQuery( this.query.limit(this._pageCount).offset(previousData.length), ); if (this.inflightRequestId || previousData !== this.data) { + retryCount++; - return this._fetchNext(); + continue; } else { if (data.length === 0) { this._hasReachedEnd = true; } else { // ... rest of the implementation } + return; } } + return; } + throw new Error('Max retries exceeded while fetching next page'); };packages/desktop-client/src/components/table.tsx (2)
819-819
: MakeisLoading
optional inSelectedItemsButtonProps
Since you've provided a default value of
false
forisLoading
in the function signature, you should make it optional in the type definition to maintain backward compatibility.Apply this diff:
- isLoading: boolean; + isLoading?: boolean;
Line range hint
842-856
: LGTM! ButtonWithLoading implementation looks good.The replacement of
Button
withButtonWithLoading
and the addition of theisLoading
prop is well implemented. The default value ensures backward compatibility.packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)
769-776
: LGTM! Improved loading state handling for schedules.The changes properly handle the loading state of schedules by adding an early return, preventing potential null reference errors and optimizing performance.
Line range hint
1765-1775
: LGTM! Added loading state prop to TransactionTableInner.The component signature has been updated to include the
isLoading
prop, following React best practices for prop drilling and state management.
1994-1994
: LGTM! Added loading state propagation to Table component.The loading state is properly propagated to the Table component, maintaining React's unidirectional data flow.
packages/loot-core/src/client/query-hooks.ts (4)
7-11
: TypeUseQueryResult<Response>
enhances type safetyThe introduction of
UseQueryResult<Response>
improves clarity and type safety of theuseQuery
function's return value.
14-16
: UpdateduseQuery
signature increases flexibilityAllowing
makeQuery
to returnQuery | null
provides flexibility for scenarios where a query might not be available, enhancing the hook's versatility.
23-25
: State initialization aligns with query availabilityInitializing
data
,isLoading
, anderror
based on thequery
value ensures that the hook's state accurately reflects the query's availability.
56-56
: Includingerror
in the return value improves error handlingReturning the
error
state allows consumers of theuseQuery
hook to handle error conditions appropriately, enhancing robustness.
const onAction = useCallback( | ||
async (name: ScheduleItemAction, id: ScheduleEntity['id']) => { | ||
switch (name) { | ||
case 'post-transaction': | ||
await send('schedule/post-transaction', { id }); | ||
break; | ||
case 'skip': | ||
await send('schedule/skip-next-date', { id }); | ||
break; | ||
case 'complete': | ||
await send('schedule/update', { | ||
schedule: { id, completed: true }, | ||
}); | ||
break; | ||
case 'restart': | ||
await send('schedule/update', { | ||
schedule: { id, completed: false }, | ||
resetNextDate: true, | ||
}); | ||
break; | ||
case 'delete': | ||
await send('schedule/delete', { id }); | ||
break; | ||
default: | ||
throw new Error(`Unknown action: ${name}`); | ||
} | ||
}, | ||
[], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for API calls
The onAction
callback makes several API calls but lacks error handling. Consider wrapping the API calls in try-catch blocks to handle potential failures gracefully.
Here's a suggested implementation:
const onAction = useCallback(
async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
+ try {
switch (name) {
case 'post-transaction':
await send('schedule/post-transaction', { id });
break;
case 'skip':
await send('schedule/skip-next-date', { id });
break;
case 'complete':
await send('schedule/update', {
schedule: { id, completed: true },
});
break;
case 'restart':
await send('schedule/update', {
schedule: { id, completed: false },
resetNextDate: true,
});
break;
case 'delete':
await send('schedule/delete', { id });
break;
default:
throw new Error(`Unknown action: ${name}`);
}
+ } catch (error) {
+ // Consider using a toast notification or error boundary
+ console.error(`Failed to perform ${name} action:`, error);
+ }
},
[],
);
Committable suggestion was skipped due to low confidence.
useEffect(() => { | ||
function setup() { | ||
return listen('sync-event', ({ type, tables }) => { | ||
if (type === 'applied') { | ||
if ( | ||
tables.includes('transactions') || | ||
tables.includes('category_mapping') || | ||
tables.includes('payee_mapping') | ||
) { | ||
paged.current?.run(); | ||
} | ||
|
||
if (tables.includes('payees') || tables.includes('payee_mapping')) { | ||
dispatch(getPayees()); | ||
} | ||
return listen('sync-event', ({ type, tables }) => { | ||
if (type === 'applied') { | ||
if ( | ||
tables.includes('transactions') || | ||
tables.includes('category_mapping') || | ||
tables.includes('payee_mapping') | ||
) { | ||
reloadTransactions?.(); | ||
} | ||
}); | ||
} | ||
|
||
fetchTransactions(); | ||
return setup(); | ||
}, [dispatch, fetchTransactions]); | ||
|
||
const updateSearchQuery = useDebounceCallback( | ||
useCallback( | ||
searchText => { | ||
if (searchText === '' && currentQuery) { | ||
updateQuery(currentQuery); | ||
} else if (searchText && currentQuery) { | ||
updateQuery( | ||
queries.makeTransactionSearchQuery( | ||
currentQuery, | ||
searchText, | ||
dateFormat, | ||
), | ||
); | ||
if (tables.includes('payees') || tables.includes('payee_mapping')) { | ||
dispatch(getPayees()); | ||
} | ||
}, | ||
[currentQuery, dateFormat, updateQuery], | ||
), | ||
150, | ||
} | ||
}); | ||
}, [dispatch, reloadTransactions]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize sync event listener implementation
The sync event handler could be optimized to reduce unnecessary checks and array recreations.
Consider this optimization:
+ const TRANSACTION_RELATED_TABLES = ['transactions', 'category_mapping', 'payee_mapping'];
+ const PAYEE_RELATED_TABLES = ['payees', 'payee_mapping'];
useEffect(() => {
return listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
- if (
- tables.includes('transactions') ||
- tables.includes('category_mapping') ||
- tables.includes('payee_mapping')
- ) {
+ if (TRANSACTION_RELATED_TABLES.some(table => tables.includes(table))) {
reloadTransactions?.();
}
- if (tables.includes('payees') || tables.includes('payee_mapping')) {
+ if (PAYEE_RELATED_TABLES.some(table => tables.includes(table))) {
dispatch(getPayees());
}
}
});
}, [dispatch, reloadTransactions]);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
useEffect(() => { | |
function setup() { | |
return listen('sync-event', ({ type, tables }) => { | |
if (type === 'applied') { | |
if ( | |
tables.includes('transactions') || | |
tables.includes('category_mapping') || | |
tables.includes('payee_mapping') | |
) { | |
paged.current?.run(); | |
} | |
if (tables.includes('payees') || tables.includes('payee_mapping')) { | |
dispatch(getPayees()); | |
} | |
return listen('sync-event', ({ type, tables }) => { | |
if (type === 'applied') { | |
if ( | |
tables.includes('transactions') || | |
tables.includes('category_mapping') || | |
tables.includes('payee_mapping') | |
) { | |
reloadTransactions?.(); | |
} | |
}); | |
} | |
fetchTransactions(); | |
return setup(); | |
}, [dispatch, fetchTransactions]); | |
const updateSearchQuery = useDebounceCallback( | |
useCallback( | |
searchText => { | |
if (searchText === '' && currentQuery) { | |
updateQuery(currentQuery); | |
} else if (searchText && currentQuery) { | |
updateQuery( | |
queries.makeTransactionSearchQuery( | |
currentQuery, | |
searchText, | |
dateFormat, | |
), | |
); | |
if (tables.includes('payees') || tables.includes('payee_mapping')) { | |
dispatch(getPayees()); | |
} | |
}, | |
[currentQuery, dateFormat, updateQuery], | |
), | |
150, | |
} | |
}); | |
}, [dispatch, reloadTransactions]); | |
const TRANSACTION_RELATED_TABLES = ['transactions', 'category_mapping', 'payee_mapping']; | |
const PAYEE_RELATED_TABLES = ['payees', 'payee_mapping']; | |
useEffect(() => { | |
return listen('sync-event', ({ type, tables }) => { | |
if (type === 'applied') { | |
if (TRANSACTION_RELATED_TABLES.some(table => tables.includes(table))) { | |
reloadTransactions?.(); | |
} | |
if (PAYEE_RELATED_TABLES.some(table => tables.includes(table))) { | |
dispatch(getPayees()); | |
} | |
} | |
}); | |
}, [dispatch, reloadTransactions]); |
type ObjectExpression = { | ||
[key: string]: ObjectExpression | unknown; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider making ObjectExpression
more specific.
The current type definition allows for any unknown value, which might be too permissive. Consider defining more specific value types based on your query requirements to catch potential type errors at compile time.
type ObjectExpression = {
- [key: string]: ObjectExpression | unknown;
+ [key: string]: ObjectExpression | string | number | boolean | null | undefined;
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
type ObjectExpression = { | |
[key: string]: ObjectExpression | unknown; | |
}; | |
type ObjectExpression = { | |
[key: string]: ObjectExpression | string | number | boolean | null | undefined; | |
}; |
function loadStatuses( | ||
schedules: readonly ScheduleEntity[], | ||
onData: (data: ScheduleStatuses) => void, | ||
onError: (error: Error) => void, | ||
upcomingLength: string, | ||
) { | ||
return liveQuery<TransactionEntity>(getHasTransactionsQuery(schedules), { | ||
onData: data => { | ||
const hasTrans = new Set(data.filter(Boolean).map(row => row.schedule)); | ||
|
||
return new Map( | ||
const scheduleStatuses = new Map( | ||
schedules.map(s => [ | ||
s.id, | ||
getStatus(s.next_date, s.completed, hasTrans.has(s.id), prefs), | ||
getStatus( | ||
s.next_date, | ||
s.completed, | ||
hasTrans.has(s.id), | ||
upcomingLength, | ||
), | ||
]), | ||
); | ||
) as ScheduleStatuses; | ||
|
||
onData?.(scheduleStatuses); | ||
}, | ||
onError, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider optimizing the Map creation.
While the implementation is correct, the Map creation could be more efficient by avoiding the intermediate array creation.
Consider this optimization:
- const scheduleStatuses = new Map(
- schedules.map(s => [
- s.id,
- getStatus(
- s.next_date,
- s.completed,
- hasTrans.has(s.id),
- upcomingLength,
- ),
- ]),
- ) as ScheduleStatuses;
+ const scheduleStatuses = new Map();
+ for (const s of schedules) {
+ scheduleStatuses.set(
+ s.id,
+ getStatus(
+ s.next_date,
+ s.completed,
+ hasTrans.has(s.id),
+ upcomingLength,
+ )
+ );
+ }
This approach:
- Avoids creating an intermediate array
- Reduces memory allocation
- Maintains the same functionality
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function loadStatuses( | |
schedules: readonly ScheduleEntity[], | |
onData: (data: ScheduleStatuses) => void, | |
onError: (error: Error) => void, | |
upcomingLength: string, | |
) { | |
return liveQuery<TransactionEntity>(getHasTransactionsQuery(schedules), { | |
onData: data => { | |
const hasTrans = new Set(data.filter(Boolean).map(row => row.schedule)); | |
return new Map( | |
const scheduleStatuses = new Map( | |
schedules.map(s => [ | |
s.id, | |
getStatus(s.next_date, s.completed, hasTrans.has(s.id), prefs), | |
getStatus( | |
s.next_date, | |
s.completed, | |
hasTrans.has(s.id), | |
upcomingLength, | |
), | |
]), | |
); | |
) as ScheduleStatuses; | |
onData?.(scheduleStatuses); | |
}, | |
onError, | |
}); | |
function loadStatuses( | |
schedules: readonly ScheduleEntity[], | |
onData: (data: ScheduleStatuses) => void, | |
onError: (error: Error) => void, | |
upcomingLength: string, | |
) { | |
return liveQuery<TransactionEntity>(getHasTransactionsQuery(schedules), { | |
onData: data => { | |
const hasTrans = new Set(data.filter(Boolean).map(row => row.schedule)); | |
const scheduleStatuses = new Map(); | |
for (const s of schedules) { | |
scheduleStatuses.set( | |
s.id, | |
getStatus( | |
s.next_date, | |
s.completed, | |
hasTrans.has(s.id), | |
upcomingLength, | |
) | |
); | |
} | |
onData?.(scheduleStatuses); | |
}, | |
onError, | |
}); |
className={css({ | ||
paddingTop: 1, | ||
paddingBottom: 1, | ||
}} | ||
[`& svg`]: { | ||
width: 10, | ||
height: 10, | ||
marginLeft: 10, | ||
color: theme.pillText, | ||
transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)', | ||
opacity: selectedItems.size > 0 || showExtraBalances ? 1 : 0, | ||
}, | ||
[`&[data-hovered] svg`]: { | ||
opacity: 1, | ||
}, | ||
})} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider extracting the CSS into a styled component.
The inline CSS-in-JS could be extracted into a styled component or a separate constant to improve readability and reusability.
const balanceButtonStyles = css({
paddingTop: 1,
paddingBottom: 1,
'& svg': {
width: 10,
height: 10,
marginLeft: 10,
color: theme.pillText,
transform: showExtraBalances ? 'rotateZ(180deg)' : 'rotateZ(0)',
opacity: selectedItems.size > 0 || showExtraBalances ? 1 : 0,
},
'&[data-hovered] svg': {
opacity: 1,
},
});
Then use it like:
<ButtonWithLoading
className={balanceButtonStyles}
// ... other props
>
useEffect(() => { | ||
return () => updateSearchQuery.cancel(); | ||
}, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix missing dependency in useEffect cleanup.
The cleanup effect is missing updateSearchQuery
in its dependency array. While this might be intentional to avoid recreating the cleanup function, it's better to be explicit about dependencies.
useEffect(() => {
return () => updateSearchQuery.cancel();
- }, []);
+ }, [updateSearchQuery]);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
useEffect(() => { | |
return () => updateSearchQuery.cancel(); | |
}, []); | |
useEffect(() => { | |
return () => updateSearchQuery.cancel(); | |
}, [updateSearchQuery]); |
🧰 Tools
🪛 GitHub Check: lint
[warning] 226-226:
React Hook useEffect has a missing dependency: 'updateSearchQuery'. Either include it or remove the dependency array
9bfd9b1
to
e32cc09
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (7)
packages/desktop-client/src/components/accounts/Balance.jsx (4)
74-76
: Consider showing a loading state instead of returning null.Returning
null
during loading might cause UI flicker. Consider showing a loading skeleton or placeholder to maintain UI stability.- if (isLoading) { - return null; - } + if (isLoading) { + return <DetailedBalance name={t('Selected balance:')} balance={0} isLoading={true} />; + }
Line range hint
90-94
: Add comments explaining the balance calculation logic.The conditional logic for adding/subtracting schedule amounts based on account ownership is not immediately obvious. Consider adding explanatory comments.
+ // Add to balance if it's a transfer from another account + // Subtract from balance if it's a transfer from this account if (accountId !== s._account) { scheduleBalance += getScheduledAmount(s._amount); } else { scheduleBalance -= getScheduledAmount(s._amount); }
117-124
: Consider adding type safety to the component props.The component could benefit from PropTypes or TypeScript type definitions to ensure proper usage.
FilteredBalance.propTypes = { filteredBalance: PropTypes.number };
157-157
: Remove commented code.There are commented lines that should be removed if they're no longer needed:
- Line 157:
// const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' });
- Line 220-221:
// transactionsQuery={transactionsQuery}
Also applies to: 220-221
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)
406-406
: LGTM! Consider enhancing loading state visibilityThe loading state is properly implemented. Consider adding a loading indicator or skeleton rows to improve user experience during data fetching.
Would you like me to provide an example implementation of skeleton rows for the loading state?
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (1)
789-792
: Consider using Context for categories and splitting component responsibilities.The component is handling multiple concerns and passing categories through multiple levels. Consider using React Context for categories and splitting the component into smaller, more focused components.
- Create a CategoryContext:
const CategoryContext = React.createContext(); export function CategoryProvider({ children }) { const { list: categories } = useCategories(); return ( <CategoryContext.Provider value={categories}> {children} </CategoryContext.Provider> ); }
- Split the component into smaller components:
ImportOptionsPanel
TransactionPreview
ImportControls
This would make the code more maintainable and reduce prop drilling.
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)
Line range hint
1765-1775
: Consider adding a default value for the isLoading prop.While the isLoading prop is correctly added, consider providing a default value to ensure predictable behavior when the prop is not passed.
function TransactionTableInner({ - isLoading, + isLoading = false, tableNavigator, tableRef, listContainerRef, dateFormat = 'MM/dd/yyyy', newNavigator, renderEmpty, onScroll, ...props })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (15)
- packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
- packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
- packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
- packages/desktop-client/src/components/table.tsx (4 hunks)
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
- packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
- packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
- packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
- packages/loot-core/src/shared/transactions.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
- packages/desktop-client/src/components/accounts/Header.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (1)
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88 Timestamp: 2024-10-22T02:08:48.162Z Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (11)
packages/loot-core/src/client/data-hooks/transactions.ts (3)
1-30
: LGTM! Well-structured imports and type definitions.The imports are properly organized and the type definitions are comprehensive and well-structured.
234-240
: LGTM! Well-implemented helper function.The
isForPreview
function is focused, readable, and effectively determines if a schedule should be previewed based on its status.
86-92
: 🛠️ Refactor suggestionConsider wrapping callback functions to prevent undefined errors.
The
reload
andloadMore
functions are directly exposed frompagedQueryRef.current
, which could be null. This might cause runtime errors if consumers don't check for undefined.return { transactions, isLoading, error, - reload: pagedQueryRef.current?.run, - loadMore: pagedQueryRef.current?.fetchNext, + reload: () => pagedQueryRef.current?.run(), + loadMore: () => pagedQueryRef.current?.fetchNext(), };Likely invalid or redundant comment.
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4)
2-8
: LGTM! Type safety improvementsThe changes improve type safety by:
- Making the schedules array readonly to prevent accidental mutations
- Adding proper loading state management through the isLoading prop
Also applies to: 39-40
Line range hint
261-276
: LGTM! Effective memoization and type safetyThe memoization of items array with proper dependencies and readonly type prevents unnecessary re-renders while maintaining immutability.
278-343
: LGTM! Effective memoization of render functionThe renderSchedule function is properly memoized with useCallback and includes all necessary dependencies, which will help prevent unnecessary re-renders.
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
345-375
: LGTM! Proper memoization of item rendererThe renderItem function is correctly memoized with appropriate dependencies, optimizing performance by preventing unnecessary re-renders.
packages/desktop-client/src/components/table.tsx (2)
819-819
:⚠️ Potential issueMake
isLoading
optional inSelectedItemsButtonProps
The
isLoading
property is marked as required in the type definition, but it has a default value in the function implementation. This inconsistency could cause TypeScript errors in existing code that usesSelectedItemsButton
without providing theisLoading
prop.Apply this diff to make the property optional:
- isLoading: boolean; + isLoading?: boolean;Likely invalid or redundant comment.
Line range hint
842-856
: LGTM: ButtonWithLoading implementationThe replacement of
Button
withButtonWithLoading
and the addition of theisLoading
prop is implemented correctly. The loading state is properly passed through to the new component.Let's verify the usage of
ButtonWithLoading
across the codebase:✅ Verification successful
Based on the search results, I can now generate the final response as I have sufficient information about the usage of
ButtonWithLoading
across the codebase.The
ButtonWithLoading
component is consistently implemented with proper loading state handlingThe usage of
ButtonWithLoading
across the codebase shows consistent implementation with theisLoading
prop being properly passed through in all instances. The component is used in various parts of the application such as settings, modals, and forms, and in each case, it correctly handles the loading state. The implementation in the table component aligns with the established pattern throughout the codebase.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for ButtonWithLoading usage to ensure consistent loading state handling rg -A 2 "ButtonWithLoading"Length of output: 23548
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)
769-776
: LGTM! Well-implemented loading state handling.The loading state check prevents accessing the schedules array before it's ready, avoiding potential runtime errors. This is a good defensive programming practice.
1994-1994
: LGTM! Proper prop drilling implementation.The loading state is correctly passed down to the Table component, maintaining consistency with the parent component's loading state.
name: `balance-query-${accountId}-cleared`, | ||
query: balanceQuery?.filter({ cleared: true }), | ||
}); | ||
const uncleared = useSheetValue({ | ||
name: balanceQuery.name + '-uncleared', | ||
query: balanceQuery.query.filter({ cleared: false }), | ||
name: `balance-query-${accountId}-uncleared`, | ||
query: balanceQuery?.filter({ cleared: false }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing the filtered queries.
The filtered queries could be memoized to prevent unnecessary recalculations.
+ const clearedQuery = useMemo(
+ () => balanceQuery?.filter({ cleared: true }),
+ [balanceQuery]
+ );
+ const unclearedQuery = useMemo(
+ () => balanceQuery?.filter({ cleared: false }),
+ [balanceQuery]
+ );
const cleared = useSheetValue({
name: `balance-query-${accountId}-cleared`,
- query: balanceQuery?.filter({ cleared: true }),
+ query: clearedQuery,
});
const uncleared = useSheetValue({
name: `balance-query-${accountId}-uncleared`,
- query: balanceQuery?.filter({ cleared: false }),
+ query: unclearedQuery,
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
name: `balance-query-${accountId}-cleared`, | |
query: balanceQuery?.filter({ cleared: true }), | |
}); | |
const uncleared = useSheetValue({ | |
name: balanceQuery.name + '-uncleared', | |
query: balanceQuery.query.filter({ cleared: false }), | |
name: `balance-query-${accountId}-uncleared`, | |
query: balanceQuery?.filter({ cleared: false }), | |
const clearedQuery = useMemo( | |
() => balanceQuery?.filter({ cleared: true }), | |
[balanceQuery] | |
); | |
const unclearedQuery = useMemo( | |
() => balanceQuery?.filter({ cleared: false }), | |
[balanceQuery] | |
); | |
const cleared = useSheetValue({ | |
name: `balance-query-${accountId}-cleared`, | |
query: clearedQuery, | |
}); | |
const uncleared = useSheetValue({ | |
name: `balance-query-${accountId}-uncleared`, | |
query: unclearedQuery, | |
}); |
const scheduleTransactions = useMemo(() => { | ||
if (isSchedulesLoading) { | ||
return []; | ||
} | ||
|
||
// Kick off an async rules application | ||
const schedulesForPreview = schedules.filter(s => | ||
isForPreview(s, statuses), | ||
); | ||
|
||
return schedulesForPreview.map(schedule => ({ | ||
id: 'preview/' + schedule.id, | ||
payee: schedule._payee, | ||
account: schedule._account, | ||
amount: schedule._amount, | ||
date: schedule.next_date, | ||
schedule: schedule.id, | ||
})); | ||
}, [isSchedulesLoading, schedules, statuses]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing schedule transformation for better performance.
The schedule transformation in the useMemo
hook could be optimized by extracting the mapping function to avoid recreating it on every render.
+const transformScheduleToPreview = (schedule: ScheduleEntity) => ({
+ id: 'preview/' + schedule.id,
+ payee: schedule._payee,
+ account: schedule._account,
+ amount: schedule._amount,
+ date: schedule.next_date,
+ schedule: schedule.id,
+});
const scheduleTransactions = useMemo(() => {
if (isSchedulesLoading) {
return [];
}
const schedulesForPreview = schedules.filter(s =>
isForPreview(s, statuses),
);
- return schedulesForPreview.map(schedule => ({
- id: 'preview/' + schedule.id,
- payee: schedule._payee,
- account: schedule._account,
- amount: schedule._amount,
- date: schedule.next_date,
- schedule: schedule.id,
- }));
+ return schedulesForPreview.map(transformScheduleToPreview);
}, [isSchedulesLoading, schedules, statuses]);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const scheduleTransactions = useMemo(() => { | |
if (isSchedulesLoading) { | |
return []; | |
} | |
// Kick off an async rules application | |
const schedulesForPreview = schedules.filter(s => | |
isForPreview(s, statuses), | |
); | |
return schedulesForPreview.map(schedule => ({ | |
id: 'preview/' + schedule.id, | |
payee: schedule._payee, | |
account: schedule._account, | |
amount: schedule._amount, | |
date: schedule.next_date, | |
schedule: schedule.id, | |
})); | |
}, [isSchedulesLoading, schedules, statuses]); | |
const transformScheduleToPreview = (schedule: ScheduleEntity) => ({ | |
id: 'preview/' + schedule.id, | |
payee: schedule._payee, | |
account: schedule._account, | |
amount: schedule._amount, | |
date: schedule.next_date, | |
schedule: schedule.id, | |
}); | |
const scheduleTransactions = useMemo(() => { | |
if (isSchedulesLoading) { | |
return []; | |
} | |
// Kick off an async rules application | |
const schedulesForPreview = schedules.filter(s => | |
isForPreview(s, statuses), | |
); | |
return schedulesForPreview.map(transformScheduleToPreview); | |
}, [isSchedulesLoading, schedules, statuses]); |
@@ -308,7 +310,7 @@ export function ImportTransactionsModal({ options }) { | |||
return next; | |||
}, []); | |||
}, | |||
[accountId, categories.list, clearOnImport, importPreviewTransactions], | |||
[accountId, categories, clearOnImport, importPreviewTransactions], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing processed transactions and caching category lookups.
The dependency array includes categories
which could cause unnecessary re-renders of preview transactions. Consider memoizing the category lookup and processed transactions.
// Add at the top of the component
const categoryMap = useMemo(
() => new Map(categories.map(cat => [cat.name, cat.id])),
[categories]
);
// Use in parseCategoryFields
const parseCategoryFields = useCallback((trans) => {
if (!trans.category) return null;
return categoryMap.get(trans.category) || trans.category;
}, [categoryMap]);
const category_id = parseCategoryFields(trans, categories); | ||
trans.category = category_id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance error handling for category processing.
The category processing lacks proper validation and error handling. Consider adding validation and more descriptive error messages.
- const category_id = parseCategoryFields(trans, categories);
- trans.category = category_id;
+ try {
+ if (!Array.isArray(categories)) {
+ throw new Error('Categories must be an array');
+ }
+ const category_id = parseCategoryFields(trans, categories);
+ trans.category = category_id;
+ } catch (error) {
+ console.error('Failed to process category:', error);
+ errorMessage = `Failed to process category for transaction on ${trans.date}: ${error.message}`;
+ break;
+ }
Committable suggestion was skipped due to low confidence.
e32cc09
to
11e9226
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Outside diff range and nitpick comments (11)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)
Line range hint
33-45
: Address the type coercion TODO comment.The current type coercion (
schedule._payee as unknown as string
) is a potential source of runtime errors. This should be properly typed to ensure type safety.Consider refactoring to use proper typing:
- describe={schedule => - describeSchedule(schedule, byId[schedule._payee as unknown as string]) - } + describe={schedule => { + if (typeof schedule._payee !== 'string') { + console.warn('Unexpected payee type:', schedule._payee); + return 'Invalid schedule'; + } + return describeSchedule(schedule, byId[schedule._payee]); + }}Also, consider updating the
ScheduleEntity
type to properly type the_payee
field.packages/loot-core/src/client/query-hooks.ts (1)
14-57
: Consider adding tests for error scenariosSince this hook is critical for the Account component migration and handles error states, consider adding comprehensive tests covering:
- Null query handling
- Error propagation
- Cleanup on unmount
- State transitions
packages/loot-core/src/shared/query.ts (2)
136-138
: Consider adding JSDoc documentation for the reset method.The
reset
method's purpose and behavior could be clearer with documentation explaining that it creates a fresh query instance for the same table.+/** + * Resets the query to its initial state while preserving the table. + * @returns {Query} A new Query instance with only the table property set. + */ reset() { return q(this.state.table); }
Line range hint
145-164
: Consider improving type safety in getPrimaryOrderBy.The function could benefit from better type narrowing and validation of order expressions.
export function getPrimaryOrderBy( query: Query, defaultOrderBy: ObjectExpression | null, ) { const orderExprs = query.serialize().orderExpressions; if (orderExprs.length === 0) { if (defaultOrderBy) { return { order: 'asc' as const, ...defaultOrderBy }; } return null; } const firstOrder = orderExprs[0]; + type OrderDirection = 'asc' | 'desc'; + if (typeof firstOrder === 'string') { - return { field: firstOrder, order: 'asc' }; + return { field: firstOrder, order: 'asc' as const }; } // Handle this form: { field: 'desc' } const [field] = Object.keys(firstOrder); + const order = firstOrder[field]; + + if (typeof order !== 'string' || !['asc', 'desc'].includes(order)) { + return { field, order: 'asc' as const }; + } - return { field, order: firstOrder[field] }; + return { field, order: order as OrderDirection }; }packages/desktop-client/src/components/accounts/Balance.jsx (3)
72-76
: Consider showing a loading indicator instead of null.Early return with
null
during loading might cause layout shifts. Consider showing a loading skeleton or placeholder to maintain visual stability.- if (isLoading) { - return null; - } + if (isLoading) { + return <DetailedBalance name={t('Selected balance:')} balance={0} isLoading={true} />; + }
Line range hint
90-94
: Improve variable naming for clarity.The condition
accountId !== s._account
could be more explicit about what it's comparing. Consider renaming variables or adding a comment to clarify the logic.- if (accountId !== s._account) { + // Add/subtract based on whether the schedule is for the current account + const isExternalAccount = accountId !== s._account; + if (isExternalAccount) {
117-124
: Add PropTypes for type safety.Consider adding PropTypes or TypeScript types to validate the
filteredBalance
prop and document its expected type.FilteredBalance.propTypes = { filteredBalance: PropTypes.number };packages/loot-core/src/client/query-helpers.ts (2)
186-191
: Consider adding validation in optimistic update.The
_optimisticUpdate
method directly updates data without validation. Consider adding validation to ensure the update function doesn't produce invalid data states.protected _optimisticUpdate = ( updateFn: (data: Data<TResponse>) => Data<TResponse>, ) => { const previousData = this.data; + const newData = updateFn(this.data); + if (newData === undefined || newData === null) { + console.warn('Optimistic update produced invalid data state'); + return; + } - this.updateData(updateFn); + this.updateData(() => newData); this.onData(this.data, previousData); };
Line range hint
383-417
: Potential deadlock in fetchNext implementation.The while loop waiting for in-flight requests could potentially deadlock if an error occurs during
this._fetchDataPromise
execution. Consider adding a timeout or error handling mechanism.private _fetchNext = async () => { + const timeout = 30000; // 30 seconds + const startTime = Date.now(); while (this.inflightRequestId) { + if (Date.now() - startTime > timeout) { + throw new Error('Timeout waiting for in-flight request'); + } await this._fetchDataPromise; } // ... rest of the implementation };packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (1)
789-792
: Add prop validation for categories in Transaction component.The categories array is passed directly to the Transaction component without validation. Consider adding PropTypes or TypeScript types to ensure type safety.
Transaction.propTypes = { categories: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired }) ).isRequired, // ... other props };packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)
Line range hint
1765-1775
: Consider adding prop documentation and type definitions.The loading state propagation is implemented correctly, but could benefit from additional documentation and type safety:
- Add JSDoc or comments explaining the purpose of the
isLoading
prop- Consider adding PropTypes or TypeScript definitions for the prop
Example documentation:
/** * @param {Object} props * @param {boolean} props.isLoading - Indicates whether the transaction data is currently being fetched */Example PropTypes:
TransactionTableInner.propTypes = { isLoading: PropTypes.bool, // ... other props };Also applies to: 1994-1994
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (2)
upcoming-release-notes/3685.md
is excluded by!**/*.md
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (44)
- .eslintrc.js (1 hunks)
- packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
- packages/desktop-client/e2e/accounts.test.js (2 hunks)
- packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
- packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
- packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
- packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
- packages/desktop-client/src/components/accounts/Balance.jsx (9 hunks)
- packages/desktop-client/src/components/accounts/Header.tsx (13 hunks)
- packages/desktop-client/src/components/accounts/Reconcile.tsx (1 hunks)
- packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
- packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
- packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
- packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (7 hunks)
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
- packages/desktop-client/src/components/rules/ScheduleValue.tsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx (6 hunks)
- packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts (2 hunks)
- packages/desktop-client/src/components/table.tsx (4 hunks)
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx (3 hunks)
- packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.jsx (4 hunks)
- packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (2 hunks)
- packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
- packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
- packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
- packages/desktop-client/src/hooks/useSelected.tsx (2 hunks)
- packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
- packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
- packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
- packages/loot-core/src/client/data-hooks/schedules.tsx (3 hunks)
- packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
- packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
- packages/loot-core/src/client/queries.ts (2 hunks)
- packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
- packages/loot-core/src/client/query-helpers.ts (4 hunks)
- packages/loot-core/src/client/query-hooks.ts (1 hunks)
- packages/loot-core/src/shared/query.ts (9 hunks)
- packages/loot-core/src/shared/transactions.ts (11 hunks)
💤 Files with no reviewable changes (2)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useSchedules.ts
🚧 Files skipped from review as they are similar to previous changes (29)
- .eslintrc.js
- packages/desktop-client/e2e/accounts.mobile.test.js
- packages/desktop-client/e2e/accounts.test.js
- packages/desktop-client/e2e/page-models/account-page.js
- packages/desktop-client/e2e/page-models/mobile-account-page.js
- packages/desktop-client/e2e/page-models/mobile-accounts-page.js
- packages/desktop-client/src/components/ManageRules.tsx
- packages/desktop-client/src/components/accounts/Header.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
- packages/desktop-client/src/components/modals/EditRuleModal.jsx
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
- packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/schedules/index.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/table.tsx
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useNotes.ts
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/client/data-hooks/dashboard.ts
- packages/loot-core/src/client/data-hooks/filters.ts
- packages/loot-core/src/client/data-hooks/reports.ts
- packages/loot-core/src/client/data-hooks/widget.ts
- packages/loot-core/src/client/queries.ts
- packages/loot-core/src/client/query-helpers.test.ts
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (3)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277 Timestamp: 2024-10-22T05:34:56.976Z Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-10-08T15:46:15.739Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming PR: actualbudget/actual#3402 File: packages/desktop-client/src/components/accounts/Account.tsx:8-8 Timestamp: 2024-09-27T14:15:46.637Z Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88 Timestamp: 2024-10-22T02:08:48.162Z Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy PR: actualbudget/actual#3685 File: packages/loot-core/src/client/data-hooks/reports.ts:8-8 Timestamp: 2024-10-18T15:37:01.917Z Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/loot-core/src/client/query-helpers.ts
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
🔇 Additional comments (36)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)
25-31
: LGTM: Well-implemented loading state with accessibility.The loading state implementation follows best practices with proper accessibility labeling and visual feedback.
packages/loot-core/src/client/query-hooks.ts (6)
7-11
: LGTM: Well-structured type definition with proper immutabilityThe
UseQueryResult
type provides a clear contract with immutable data and proper error handling support.
14-16
: LGTM: Improved signature with nullable query supportThe updated signature with
Query | null
return type adds flexibility while maintaining type safety.
17-21
: LGTM: Proper memoization with clear documentationThe memoization strategy is well-implemented and the ESLint disable is properly documented.
23-25
: LGTM: Clean state management with proper typingThe state management is well-structured with appropriate initial values and TypeScript types.
35-38
: Consider using useRef for unmount flagThe previous review comment about using
useRef
for theisUnmounted
flag is still valid to prevent potential stale closure issues.
52-57
: LGTM: Complete return value matching type definitionThe return value properly includes all necessary states as defined in
UseQueryResult
.packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (5)
1-8
: LGTM! Clean hook imports and usage.The migration to hooks is well-structured with proper imports from the new data-hooks directory.
28-34
: Skip comment - existing review is still valid.The previous review comment about memoizing the filter object is still applicable to this code segment.
37-47
: LGTM! Clean implementation of transaction state management.The component effectively utilizes the new useTransactions hook, properly managing transaction state and related functionalities.
51-67
: Skip comment - existing review is still valid.The previous review comment about optimizing the sync event listener implementation is still applicable to this code segment.
75-82
: LGTM! Well-documented transaction handler.The implementation is clean and the comment provides valuable context about preview transaction handling.
packages/loot-core/src/shared/query.ts (2)
8-13
: LGTM! Well-structured type definitions for QueryState.The type definitions are clear and properly constrained, making good use of union types for expressions.
Line range hint
25-40
: LGTM! Well-implemented constructor with proper type safety.Good use of
WithRequired
to enforce thetable
property while keeping other properties optional. The defaults are properly initialized before the spread operator.packages/desktop-client/src/components/accounts/Balance.jsx (1)
158-165
: LGTM! Good use of useMemo.The memoization is correctly implemented with appropriate dependencies, which will help prevent unnecessary recalculations.
packages/loot-core/src/client/data-hooks/transactions.ts (2)
229-231
:⚠️ Potential issueFix missing dependency in useEffect cleanup.
The cleanup effect is missing
updateSearchQuery
in its dependency array.Apply this diff to fix the dependency array:
useEffect(() => { return () => updateSearchQuery.cancel(); - }, []); + }, [updateSearchQuery]);Likely invalid or redundant comment.
213-227
:⚠️ Potential issueEnhance search input handling and state management.
The current implementation has several issues:
- Search function doesn't handle undefined/null input
- isSearching state remains true even after search completes
- Missing proper input sanitization
Apply this diff to improve the implementation:
const updateSearchQuery = useMemo( () => debounce((searchText: string) => { + const trimmedText = searchText?.trim() ?? ''; - if (searchText === '') { + if (trimmedText === '') { resetQuery(); setIsSearching(false); - } else if (searchText) { + } else { updateQuery(previousQuery => - queries.transactionsSearch(previousQuery, searchText, dateFormat), + queries.transactionsSearch(previousQuery, trimmedText, dateFormat), ); setIsSearching(true); + // Reset isSearching after the debounce delay + setTimeout(() => setIsSearching(false), delayMs); } }, delayMs), [dateFormat, delayMs, resetQuery, updateQuery], );Likely invalid or redundant comment.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (5)
21-28
: LGTM: Clean import organization for new hooksThe new imports for
useTransactions
,useTransactionsSearch
, andusePreviewTransactions
align well with the PR's objective of migrating to functional components with hooks.
59-62
: LGTM: Proper memoization of schedules queryThe
useMemo
implementation correctly memoizes the schedules query with appropriate dependency onaccountId
.
Line range hint
106-155
: LGTM: Well-structured callback implementationsThe callbacks are properly memoized using
useCallback
with correct dependency arrays, following React best practices for performance optimization.
217-222
: LGTM: Improved type safety with precise type definitionsThe enhanced type definitions for
accountId
andaccountName
provide better type safety and clarity using proper union types and AccountEntity references.
224-240
: 🛠️ Refactor suggestionConsider memoizing the options object in baseTransactionsQuery
While the current implementation is correct, consider memoizing the options object to prevent unnecessary re-renders:
const baseTransactionsQuery = useCallback( - () => - queries.transactions(accountId).options({ splits: 'none' }).select('*'), + () => { + const options = useMemo(() => ({ splits: 'none' }), []); + return queries.transactions(accountId).options(options).select('*'); + }, [accountId], );Likely invalid or redundant comment.
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (5)
2-8
: LGTM: Type safety improvementsGood improvements in type safety:
- Added necessary hooks for functional component migration
- Made schedules prop readonly to enforce immutability
Also applies to: 39-40
207-207
: LGTM: Proper loading state handlingGood improvements:
- Added loading state with sensible default
- Consistent use of readonly type for items
Also applies to: 261-261
278-343
: LGTM: Performance optimization with useCallbackGood use of useCallback for memoizing render functions with proper dependency arrays.
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
325-327
: Simplify nested condition using optional chaining🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
406-406
: LGTM: Proper loading state propagationLoading state is correctly passed to the Table component.
packages/loot-core/src/client/query-helpers.ts (2)
10-58
: Well-structured type definitions and function signatures!The introduction of TypeScript types and generics significantly improves type safety and API clarity. The structured parameter objects make the API more maintainable and self-documenting.
422-425
: LGTM: Optimistic update with count adjustment.The optimistic update implementation correctly adjusts the total count based on the data length difference, maintaining data consistency.
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (2)
243-246
: Previous performance optimization comment is still applicable.The linear search implementation in
parseCategoryFields
remains unchanged.
587-588
:⚠️ Potential issueImprove category assignment error handling.
The current implementation assigns
category_id
totrans.category
without validation. This could lead to null categories being assigned when they shouldn't be.-const category_id = parseCategoryFields(trans, categories); -trans.category = category_id; +const category_id = parseCategoryFields(trans, categories); +if (trans.category && !category_id) { + errorMessage = `Unable to find matching category for "${trans.category}"`; + break; +} +trans.category = category_id;Likely invalid or redundant comment.
packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (2)
8-8
: LGTM: Clean import addition for SchedulesProviderThe import statement is correctly placed and follows the existing import organization pattern.
149-171
: LGTM: Clean integration of SchedulesProviderThe SchedulesProvider is correctly integrated into the component hierarchy:
- Maintains proper nesting order
- Preserves all existing props and child components
- Follows the established component composition pattern
Let's verify that the SchedulesProvider is consistently used across other test files:
✅ Verification successful
SchedulesProvider usage is consistent across the codebase
The verification shows that SchedulesProvider is consistently used across the codebase:
- In
TransactionsTable.test.jsx
, it's correctly wrapped around the transaction table components- In production code, it's properly used in
Account.tsx
andAccountTransactions.tsx
with the required query prop- The provider's implementation in
schedules.tsx
confirms it's designed to be used as shown in the test file🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for consistent usage of SchedulesProvider in test files # Look for other test files that might need the same wrapper # Search for test files using TransactionTable fd -e test.jsx -e test.js | xargs rg "TransactionTable" # Check for existing SchedulesProvider usage rg "SchedulesProvider" -A 2 -B 2Length of output: 6433
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)
769-776
: Great improvement to loading state handling!The changes effectively prevent potential null reference errors and optimize performance by:
- Using the
useCachedSchedules
hook to manage loading state- Adding an early return during loading
- Moving the schedule lookup after the loading check
packages/loot-core/src/client/data-hooks/schedules.tsx (2)
89-94
: Good practice: Preventing state updates after unmountThe implementation of
onError
now correctly checksisUnmounted
before updating the state, which prevents potential memory leaks caused by setting state after the component has unmounted.
101-125
: Proper cleanup of subscriptionsThe cleanup function in the
useEffect
hook properly unsubscribes fromscheduleQueryRef.current
andstatusQueryRef.current
. This ensures that all subscriptions are terminated when the component unmounts, preventing memory leaks.
const schedulesQuery = useMemo(() => q('schedules').select('*'), []); | ||
const { schedules = [], isLoading } = useSchedules({ query: schedulesQuery }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider optimizing the schedules query.
The current query selects all fields (*
) from schedules. Consider selecting only the required fields to optimize data transfer and processing.
- const schedulesQuery = useMemo(() => q('schedules').select('*'), []);
+ const schedulesQuery = useMemo(
+ () => q('schedules').select(['id', '_payee', 'name', /* other required fields */]),
+ []
+ );
Committable suggestion was skipped due to low confidence.
const { search: onSearch } = useTransactionsSearch({ | ||
updateQuery: setTransactionsQuery, | ||
resetQuery: () => setTransactionsQuery(baseTransactionsQuery()), | ||
dateFormat, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing the resetQuery callback.
The resetQuery callback could be memoized to prevent unnecessary recreations.
+ const resetQuery = useCallback(
+ () => setTransactionsQuery(baseTransactionsQuery()),
+ [baseTransactionsQuery]
+ );
const { search: onSearch } = useTransactionsSearch({
updateQuery: setTransactionsQuery,
- resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
+ resetQuery,
dateFormat,
});
Committable suggestion was skipped due to low confidence.
{showExtraBalances && balanceQuery && ( | ||
<MoreBalances | ||
accountId={accountId} | ||
// transactionsQuery={transactionsQuery} | ||
balanceQuery={balanceQuery} | ||
/> | ||
</Button> | ||
{showExtraBalances && <MoreBalances balanceQuery={balanceQuery} />} | ||
|
||
)} | ||
{selectedItems.size > 0 && ( | ||
<SelectedBalance selectedItems={selectedItems} account={account} /> | ||
<SelectedBalance selectedItems={selectedItems} accountId={accountId} /> | ||
)} | ||
{showFilteredBalance && ( | ||
<FilteredBalance filteredBalance={filteredBalance} /> | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider extracting balance display logic into a separate component.
The conditional rendering logic could be simplified by extracting it into a dedicated component, improving readability and maintainability.
function BalanceDisplays({
showExtraBalances,
balanceQuery,
accountId,
selectedItems,
showFilteredBalance,
filteredBalance
}) {
return (
<>
{showExtraBalances && balanceQuery && (
<MoreBalances accountId={accountId} balanceQuery={balanceQuery} />
)}
{selectedItems.size > 0 && (
<SelectedBalance selectedItems={selectedItems} accountId={accountId} />
)}
{showFilteredBalance && (
<FilteredBalance filteredBalance={filteredBalance} />
)}
</>
);
}
reload: pagedQueryRef.current?.run, | ||
loadMore: pagedQueryRef.current?.fetchNext, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide safe no-op functions for reload and loadMore.
The current implementation could return undefined functions when pagedQueryRef.current
is null, which might cause runtime errors.
Apply this diff to provide safe no-op functions:
- reload: pagedQueryRef.current?.run,
- loadMore: pagedQueryRef.current?.fetchNext,
+ reload: () => pagedQueryRef.current?.run(),
+ loadMore: () => pagedQueryRef.current?.fetchNext(),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
reload: pagedQueryRef.current?.run, | |
loadMore: pagedQueryRef.current?.fetchNext, | |
}; | |
reload: () => pagedQueryRef.current?.run(), | |
loadMore: () => pagedQueryRef.current?.fetchNext(), | |
}; |
return; | ||
} | ||
|
||
setIsLoading(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve loading state management.
The loading state is set to true before validating the query, which could lead to unnecessary loading states. Consider moving this after the query validation.
Apply this diff to fix the loading state management:
- setIsLoading(true);
if (query.state.table !== 'transactions') {
onError(new Error('Query must be a transactions query.'));
return;
}
+ setIsLoading(true);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
setIsLoading(true); | |
if (query.state.table !== 'transactions') { | |
onError(new Error('Query must be a transactions query.')); | |
return; | |
} | |
setIsLoading(true); |
Promise.all( | ||
scheduleTransactions.map(transaction => | ||
send('rules-run', { transaction }), | ||
), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add cleanup for pending promises.
The Promise.all
chain could continue executing after component unmount. While state updates are protected, the rules-run
operations will still complete unnecessarily.
Apply this diff to add cleanup:
+ const abortController = new AbortController();
Promise.all(
scheduleTransactions.map(transaction =>
- send('rules-run', { transaction }),
+ send('rules-run', { transaction, signal: abortController.signal }),
),
)
.then(newTrans => {
if (!isUnmounted) {
// ... existing code ...
}
})
.catch(error => {
- if (!isUnmounted) {
+ if (!isUnmounted && error.name !== 'AbortError') {
setError(error);
setIsLoading(false);
}
});
return () => {
isUnmounted = true;
+ abortController.abort();
};
Committable suggestion was skipped due to low confidence.
const transactionsToDisplay = !isSearching | ||
? previewTransactions.concat(transactions) | ||
: transactions; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing transactionsToDisplay
The transaction concatenation operation could be expensive for large lists. Consider memoizing the result:
- const transactionsToDisplay = !isSearching
- ? previewTransactions.concat(transactions)
- : transactions;
+ const transactionsToDisplay = useMemo(
+ () => (!isSearching ? previewTransactions.concat(transactions) : transactions),
+ [isSearching, previewTransactions, transactions]
+ );
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const transactionsToDisplay = !isSearching | |
? previewTransactions.concat(transactions) | |
: transactions; | |
const transactionsToDisplay = useMemo( | |
() => (!isSearching ? previewTransactions.concat(transactions) : transactions), | |
[isSearching, previewTransactions, transactions] | |
); | |
const renderItem = useCallback( | ||
({ item }: { item: SchedulesTableItem }) => { | ||
if (item.id === 'show-completed') { | ||
return ( | ||
<Row | ||
height={ROW_HEIGHT} | ||
inset={15} | ||
style={{ | ||
cursor: 'pointer', | ||
backgroundColor: 'transparent', | ||
':hover': { backgroundColor: theme.tableRowBackgroundHover }, | ||
}} | ||
onClick={() => setShowCompleted(true)} | ||
> | ||
<Field | ||
width="flex" | ||
style={{ | ||
fontStyle: 'italic', | ||
textAlign: 'center', | ||
color: theme.tableText, | ||
}} | ||
> | ||
<Trans>Show completed schedules</Trans> | ||
</Field> | ||
</Row> | ||
); | ||
} | ||
return renderSchedule({ schedule: item as ScheduleEntity }); | ||
}, | ||
[renderSchedule], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using a type predicate for safer type narrowing
Instead of using type casting, consider using a type predicate for better type safety.
function isScheduleEntity(item: SchedulesTableItem): item is ScheduleEntity {
return 'completed' in item;
}
// Then in renderItem:
return isScheduleEntity(item) ? renderSchedule({ schedule: item }) : null;
@@ -5,6 +5,7 @@ import deepEqual from 'deep-equal'; | |||
import { amountToInteger } from 'loot-core/src/shared/util'; | |||
|
|||
import { useActions } from '../../../hooks/useActions'; | |||
import { useCategories } from '../../../hooks/useCategories'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing categories to optimize performance.
The categories list from useCategories
hook is used in multiple callback dependencies. Consider memoizing it to prevent unnecessary re-renders of the preview and import logic.
-const { list: categories } = useCategories();
+const { list: categoriesList } = useCategories();
+const categories = useMemo(() => categoriesList, [categoriesList]);
Also applies to: 159-159
11e9226
to
b669447
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (15)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)
25-31
: Consider extracting the loading indicator into a reusable styled component.While the loading state handling is good, the inline styles could be moved to a styled component for better maintainability and reuse.
+const LoadingContainer = styled(View)` + display: inline-flex; +`; if (isLoading) { return ( - <View aria-label="Loading..." style={{ display: 'inline-flex' }}> + <LoadingContainer aria-label="Loading..."> <AnimatedLoading width={10} height={10} /> - </View> + </LoadingContainer> ); }packages/desktop-client/src/components/schedules/index.tsx (1)
41-69
: Add dispatch to onAction dependencies.The
onAction
callback should includedispatch
in its dependency array for consistency with other callbacks and to comply with the project's conventions, even though it's not currently using dispatch.}, - [], + [dispatch], );packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (1)
75-82
: Improve the code comment about preview transactions.The comment referencing a specific commit hash could be more descriptive. Consider adding a brief explanation of what preview transactions are and why they need special handling.
- // details of how the native app used to handle preview transactions here can be found at commit 05e58279 + // Preview transactions (temporary transactions not yet saved) are handled differently in native apps + // and should not navigate to the transaction details page. See commit 05e58279 for historical context.packages/loot-core/src/shared/query.ts (1)
Line range hint
145-164
: Consider validating order values in getPrimaryOrderBy.The function assumes string orders are always 'asc' and doesn't validate order values. Consider adding validation to ensure order values are either 'asc' or 'desc'.
export function getPrimaryOrderBy( query: Query, defaultOrderBy: ObjectExpression | null, ) { + type OrderDirection = 'asc' | 'desc'; + const isValidOrder = (order: unknown): order is OrderDirection => + order === 'asc' || order === 'desc'; const orderExprs = query.serialize().orderExpressions; if (orderExprs.length === 0) { if (defaultOrderBy) { + const order = defaultOrderBy.order; + if (!isValidOrder(order)) { + throw new Error(`Invalid order: ${order}`); + } return { order: 'asc', ...defaultOrderBy }; } return null; } const firstOrder = orderExprs[0]; if (typeof firstOrder === 'string') { return { field: firstOrder, order: 'asc' }; } const [field] = Object.keys(firstOrder); + const order = firstOrder[field]; + if (!isValidOrder(order)) { + throw new Error(`Invalid order: ${order}`); + } - return { field, order: firstOrder[field] }; + return { field, order }; }packages/desktop-client/src/components/accounts/Balance.jsx (2)
72-76
: Consider adding a loading indicator for better user experience.While returning
null
during loading is a valid approach, users might benefit from seeing a loading state. Consider showing a skeleton loader or spinner during the loading phase.const { isLoading, schedules = [] } = useCachedSchedules(); if (isLoading) { - return null; + return <LoadingSpinner size="small" />; }
Line range hint
90-94
: Add comments explaining the account balance adjustment logic.The account comparison and subsequent balance adjustment (positive vs negative) contain important business logic that should be documented for future maintenance.
+ // Add to balance if it's a transfer to another account + // Subtract from balance if it's a transaction in this account if (accountId !== s._account) { scheduleBalance += getScheduledAmount(s._amount); } else { scheduleBalance -= getScheduledAmount(s._amount); }packages/loot-core/src/client/data-hooks/transactions.ts (1)
105-191
: Consider adding error boundary protection.The hook handles errors well internally, but consider wrapping the component using this hook with an error boundary to gracefully handle any unhandled errors during schedule processing.
packages/desktop-client/src/components/transactions/TransactionList.jsx (2)
Line range hint
100-117
: LGTM: Smart refresh optimizationThe conditional refresh logic based on date changes is well thought out. It optimizes performance by only triggering a full refresh when the transaction order might change.
Consider extracting the timestamp generation to a constant for better readability:
- changes.diff.updated[0].sort_order = Date.now(); + const currentTimestamp = Date.now(); + changes.diff.updated[0].sort_order = currentTimestamp;
Line range hint
200-243
: Consider reducing prop drillingThe
TransactionTable
component receives a large number of props. While this works, it could make the component harder to maintain and test.Consider these improvements for future iterations:
- Create a TransactionContext to manage shared state
- Break down the component into smaller, more focused components
- Extract some of the callback props into custom hooks
This would align well with the PR's objective of moving towards hooks and functional components.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (1)
113-115
: Consider adding error handling for notes savingThe
onSaveNotes
callback should handle potential errors from thesend
operation to provide better user feedback.const onSaveNotes = useCallback(async (id: string, notes: string) => { + try { await send('notes-save', { id, note: notes }); + } catch (error) { + console.error('Failed to save notes:', error); + // Consider showing a user-friendly error notification + } }, []);packages/desktop-client/src/components/schedules/SchedulesTable.tsx (1)
Line range hint
405-411
: Improve type safety by avoiding type assertionThe type assertion
items as ScheduleEntity[]
could be avoided by properly typing the Table component:<Table<SchedulesTableItem> loading={isLoading} rowHeight={ROW_HEIGHT} backgroundColor="transparent" style={{ flex: 1, backgroundColor: 'transparent', ...style }} items={items} renderItem={renderItem} renderEmpty={filter ? t('No matching schedules') : t('No schedules')} />packages/loot-core/src/client/query-helpers.ts (3)
4-4
: Consider using a more specific import for Query type.The Query type import could be more specific to improve code maintainability. Consider importing only the specific Query interface needed.
-import { getPrimaryOrderBy, type Query } from '../shared/query'; +import type { Query } from '../shared/query'; +import { getPrimaryOrderBy } from '../shared/query';
55-57
: Add JSDoc documentation for LiveQueryOptions type.Adding documentation would help explain the purpose of the
onlySync
option and its implications.+/** + * Options for configuring LiveQuery behavior + * @property onlySync - When true, only responds to 'success' sync events. + * When false, responds to both 'applied' and 'success' sync events. + */ type LiveQueryOptions = { onlySync?: boolean; };
Line range hint
324-375
: Add JSDoc documentation for refetchUpToRow method.This complex method would benefit from detailed documentation explaining its purpose, parameters, and behavior.
+/** + * Refetches data up to a specific row, maintaining the query's order. + * This is useful when you need to ensure all data up to a specific row is loaded, + * for example, when scrolling to a specific position. + * + * @param id - The ID of the row to fetch up to + * @param defaultOrderBy - Default ordering to use if query doesn't specify one + * @throws {Error} When the query doesn't have an orderBy clause + * @returns Promise that resolves when the data has been refetched + */ refetchUpToRow = async (id, defaultOrderBy) => {🧰 Tools
🪛 Biome
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)
Line range hint
1765-1775
: Add JSDoc for the newisLoading
prop.The
isLoading
prop has been added but lacks documentation. Consider adding JSDoc to describe its purpose and type.Add this documentation above the component:
/** * @typedef {Object} TransactionTableInnerProps * @property {boolean} isLoading - Indicates whether the transaction data is currently being loaded * ...existing props */Also applies to: 1994-1994
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (2)
upcoming-release-notes/3685.md
is excluded by!**/*.md
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (44)
.eslintrc.js
(1 hunks)packages/desktop-client/e2e/accounts.mobile.test.js
(3 hunks)packages/desktop-client/e2e/accounts.test.js
(2 hunks)packages/desktop-client/e2e/page-models/account-page.js
(1 hunks)packages/desktop-client/e2e/page-models/mobile-account-page.js
(2 hunks)packages/desktop-client/e2e/page-models/mobile-accounts-page.js
(1 hunks)packages/desktop-client/src/components/ManageRules.tsx
(2 hunks)packages/desktop-client/src/components/accounts/Balance.jsx
(9 hunks)packages/desktop-client/src/components/accounts/Header.tsx
(13 hunks)packages/desktop-client/src/components/accounts/Reconcile.tsx
(1 hunks)packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx
(6 hunks)packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
(1 hunks)packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx
(3 hunks)packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
(0 hunks)packages/desktop-client/src/components/modals/EditRuleModal.jsx
(2 hunks)packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx
(7 hunks)packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
(3 hunks)packages/desktop-client/src/components/rules/ScheduleValue.tsx
(2 hunks)packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
(2 hunks)packages/desktop-client/src/components/schedules/ScheduleLink.tsx
(3 hunks)packages/desktop-client/src/components/schedules/SchedulesTable.tsx
(6 hunks)packages/desktop-client/src/components/schedules/index.tsx
(3 hunks)packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
(2 hunks)packages/desktop-client/src/components/table.tsx
(4 hunks)packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
(3 hunks)packages/desktop-client/src/components/transactions/TransactionList.jsx
(2 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.jsx
(4 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx
(2 hunks)packages/desktop-client/src/hooks/useNotes.ts
(1 hunks)packages/desktop-client/src/hooks/usePreviewTransactions.ts
(1 hunks)packages/desktop-client/src/hooks/useSchedules.ts
(0 hunks)packages/desktop-client/src/hooks/useSelected.tsx
(2 hunks)packages/loot-core/src/client/data-hooks/dashboard.ts
(1 hunks)packages/loot-core/src/client/data-hooks/filters.ts
(2 hunks)packages/loot-core/src/client/data-hooks/reports.ts
(3 hunks)packages/loot-core/src/client/data-hooks/schedules.tsx
(3 hunks)packages/loot-core/src/client/data-hooks/transactions.ts
(1 hunks)packages/loot-core/src/client/data-hooks/widget.ts
(1 hunks)packages/loot-core/src/client/queries.ts
(2 hunks)packages/loot-core/src/client/query-helpers.test.ts
(17 hunks)packages/loot-core/src/client/query-helpers.ts
(4 hunks)packages/loot-core/src/client/query-hooks.ts
(1 hunks)packages/loot-core/src/shared/query.ts
(9 hunks)packages/loot-core/src/shared/transactions.ts
(11 hunks)
💤 Files with no reviewable changes (2)
- packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useSchedules.ts
🚧 Files skipped from review as they are similar to previous changes (26)
- .eslintrc.js
- packages/desktop-client/e2e/accounts.mobile.test.js
- packages/desktop-client/e2e/accounts.test.js
- packages/desktop-client/e2e/page-models/account-page.js
- packages/desktop-client/e2e/page-models/mobile-account-page.js
- packages/desktop-client/e2e/page-models/mobile-accounts-page.js
- packages/desktop-client/src/components/ManageRules.tsx
- packages/desktop-client/src/components/accounts/Header.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
- packages/desktop-client/src/components/modals/EditRuleModal.jsx
- packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx
- packages/desktop-client/src/components/schedules/ScheduleDetails.jsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx
- packages/desktop-client/src/hooks/useNotes.ts
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/client/data-hooks/dashboard.ts
- packages/loot-core/src/client/data-hooks/filters.ts
- packages/loot-core/src/client/data-hooks/reports.ts
- packages/loot-core/src/client/data-hooks/widget.ts
- packages/loot-core/src/client/query-helpers.test.ts
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (4)
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277
Timestamp: 2024-10-22T05:34:56.976Z
Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
packages/desktop-client/src/components/schedules/index.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:33-35
Timestamp: 2024-10-22T05:32:57.033Z
Learning: In the project's React components, when using `dispatch` from `useDispatch` in `useCallback` or `useEffect` hooks, `dispatch` must be included in the dependency array due to the `react-hooks/exhaustive-deps` ESLint rule.
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:37-39
Timestamp: 2024-10-22T05:32:30.530Z
Learning: In our React function components, we should include `dispatch` in the dependency array of `useCallback` hooks to comply with ESLint rules, even though `dispatch` is a stable function.
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:26-31
Timestamp: 2024-10-22T05:32:55.520Z
Learning: In this codebase, ESLint requires `dispatch` from `useDispatch` to be included in the dependency array of `useCallback` hooks, even though `dispatch` is stable.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/loot-core/src/client/query-helpers.ts
[error] 61-61: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
🔇 Additional comments (46)
packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)
Line range hint 34-45
: Address the TODO comment regarding payee type coercion.
The current type coercion (schedule._payee as unknown as string
) is a code smell and could lead to runtime errors. Consider updating the schedule type definition to properly type the _payee
field.
Let's check the schedule type definition and usage:
Consider one of these solutions:
- Update the ScheduleEntity type to properly type the _payee field
- Create a proper type guard to ensure type safety
- Update the describeSchedule function to handle different payee types
packages/loot-core/src/client/query-hooks.ts (6)
7-11
: LGTM! Well-structured type definition.
The UseQueryResult
type effectively captures the hook's return value with immutable data array and proper error handling support.
14-16
: LGTM! Improved function signature.
The changes enhance type safety and clarity:
- Allowing null queries enables graceful error handling
- Renamed parameter is more descriptive
- Return type is explicitly specified
17-21
: LGTM! Well-documented memoization.
The memoization strategy is appropriate and the ESLint disable is well-justified with a clear comment explaining the rationale.
23-25
: LGTM! Clean state management.
The state variables are well-typed and initialized with appropriate default values. The loading state correctly reflects the query's existence.
27-50
: Previous comment about useRef is still applicable.
The implementation of the effect hook is solid, but the concern about using a local variable for unmount tracking remains valid.
52-57
: LGTM! Consistent return value.
The return value matches the UseQueryResult
type definition and includes all necessary fields.
packages/desktop-client/src/components/schedules/index.tsx (3)
Line range hint 1-23
: LGTM! Clean migration to functional component patterns.
The changes effectively migrate from class component patterns to functional component patterns, using modern React hooks and direct Redux dispatch.
71-76
: LGTM! Improved data fetching with proper loading state handling.
The changes effectively optimize query handling through memoization and improve loading state management.
112-112
: LGTM! Proper loading state propagation.
The addition of the isLoading
prop ensures proper loading state handling in the child component.
packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (6)
1-8
: LGTM: Clean import structure and proper hook usage.
The imports are well-organized and the new transaction hooks are properly imported.
28-34
: Consider memoizing the filter object.
A past review comment already suggested memoizing the filter object to prevent unnecessary query reconstructions. This suggestion is still valid.
37-47
: LGTM: Clean implementation of useTransactions hook.
The transition to the useTransactions hook is well-implemented, with proper state management and destructuring of necessary operations.
51-67
: Optimize sync event listener implementation.
A past review comment already suggested optimizing the sync event handler to reduce unnecessary checks. This suggestion is still valid.
69-73
: Consider memoizing the resetQuery callback.
A past review comment already suggested memoizing the resetQuery callback to prevent unnecessary recreations. This suggestion is still valid.
Line range hint 89-118
: LGTM: Clean rendering implementation.
The component's rendering logic is well-structured with proper prop passing and clear separation of concerns.
packages/loot-core/src/shared/query.ts (4)
8-13
: LGTM! QueryState type improvements enhance type safety.
The updated QueryState
type with specific types for expressions and required table property improves type safety and makes the API contract clearer.
Line range hint 25-40
: LGTM! Constructor properly enforces required properties.
The use of WithRequired
utility type ensures the table property is always provided while keeping other properties optional. The initialization with defaults is clean and type-safe.
Line range hint 67-84
: LGTM! Method signatures properly typed.
The updated method signatures with specific types for parameters improve type safety while maintaining flexibility through union types where appropriate.
Also applies to: 90-116, 132-134
166-168
: LGTM! Query factory function properly typed.
The q
function is correctly typed to ensure the table parameter matches the QueryState
table type.
packages/loot-core/src/client/data-hooks/schedules.tsx (3)
120-124
: Well-implemented cleanup logic!
The cleanup function properly unsubscribes from both queries and sets the unmount flag, effectively preventing memory leaks and ensuring proper resource cleanup.
154-159
: Well-crafted error message in useCachedSchedules!
The error message clearly indicates the requirement to use the hook within a provider context, which will help developers quickly identify and fix integration issues.
169-173
: Verify handling of closed accounts for uncategorized schedules.
When accountId
is 'uncategorized', the query still includes the filter for non-closed accounts (_account.closed: false
). Is this intentional? If uncategorized schedules should be shown regardless of the account's closed status, consider conditionally applying the closed account filter.
Let's verify the current behavior:
Also applies to: 176-178
✅ Verification successful
Based on the code review, I can now provide a final response regarding the handling of closed accounts in uncategorized schedules.
The closed account filter is intentional and should be kept.
Looking at the accountFilter
implementation in queries.ts
, the closed account filtering is consistently applied across different account types, including 'budgeted', 'offbudget', and by extension, 'uncategorized'. This is the correct behavior because:
- The server-side schedule service (
schedules/app.ts
) also filters out closed accounts when advancing schedules:filter({ completed: false, '_account.closed': false })
- Closed accounts are intentionally excluded from schedule processing as shown in the
advanceSchedulesService
function - There's no special handling for uncategorized schedules with closed accounts anywhere in the codebase
This consistent treatment of closed accounts across all schedule types helps maintain data integrity by preventing schedules from being processed for accounts that are no longer active.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if there are any uncategorized schedules with closed accounts that might be unintentionally filtered out
# Search for related tests or code that might indicate the intended behavior
rg -A 5 "uncategorized.*schedule.*closed"
# Search for any documentation about handling closed accounts
fd -e md -e txt | xargs rg "closed.*account.*schedule|schedule.*closed.*account"
Length of output: 126
Script:
#!/bin/bash
# Let's try a broader search to understand the handling of schedules and closed accounts
# Search for schedule-related queries and filters
rg -l "schedules.*closed|closed.*schedules"
# Look for schedule-related tests
fd -e test.ts -e test.tsx -e spec.ts -e spec.tsx | xargs rg "schedule.*account"
# Search for schedule filtering logic
ast-grep --pattern 'filter({
$$$
closed: $_
$$$
})'
# Look for schedule-related types and interfaces
ast-grep --pattern 'interface $_ {
$$$
closed: $_
$$$
}'
Length of output: 846
Script:
#!/bin/bash
# Let's check the specific file mentioned in the results and look for schedule handling
cat packages/loot-core/src/server/schedules/app.ts
# Look for any schedule-related queries that handle account status
rg -A 5 "schedules.*account" packages/loot-core/src/server/schedules/app.ts
# Search for schedule filtering in the client code
rg -A 5 "accountSchedulesQuery|scheduleQuery" packages/loot-core/src/client/
# Look for any schedule-related tests that might indicate intended behavior
fd . packages/loot-core/src/client/data-hooks/__tests__ packages/loot-core/src/server/schedules/__tests__ 2>/dev/null | xargs rg -l "schedule.*test|test.*schedule"
Length of output: 20494
Script:
#!/bin/bash
# Let's check the implementation of accountFilter function and how it handles closed accounts
rg -A 10 "function accountFilter" packages/loot-core/src/client/
# Check the e2e test for schedules to understand the expected behavior
cat packages/desktop-client/e2e/schedules.test.js
# Look for any code that handles uncategorized schedules specifically
rg -A 5 "uncategorized.*schedule" packages/loot-core/src/client/
Length of output: 3926
packages/desktop-client/src/components/accounts/Balance.jsx (2)
158-165
: Good use of memoization for performance optimization.
The memoization of the balance binding with appropriate dependencies will prevent unnecessary recalculations.
176-177
: 🛠️ Refactor suggestion
Consider a more comprehensive loading state check.
The loading state currently only depends on balanceQuery
existence. Consider including other loading states (like useCachedSchedules().isLoading
) for a more accurate loading indicator.
<ButtonWithLoading
- isLoading={!balanceQuery}
+ isLoading={!balanceQuery || useCachedSchedules().isLoading}
data-testid="account-balance"
packages/loot-core/src/client/data-hooks/transactions.ts (2)
1-16
: LGTM! Well-organized imports and type definitions.
The imports are logically organized and the type definitions are properly scoped.
239-245
: LGTM! Clear and concise utility function.
The function has clear logic for determining preview eligibility based on schedule status.
packages/desktop-client/src/components/transactions/TransactionList.jsx (2)
60-60
: LGTM: Loading state management enhancement
The addition of the isLoading
prop and its propagation to TransactionTable
improves the component's ability to handle loading states, which is a good practice for better UX.
Also applies to: 201-201
Line range hint 92-98
: Verify the removal of onRefetch in onAdd
The removal of onRefetch()
after saving new transactions might lead to stale data in other components that depend on immediate updates.
Let's verify if there are any components that might be affected:
✅ Verification successful
Based on the search results and the original code context, I can now generate the final response:
Removal of onRefetch is safe in this context
The code is using a ref (transactionsLatest
) to maintain the latest state, and the changes are properly managed through Redux dispatch actions. The removal of onRefetch
is safe because:
- The component maintains its own latest state through
useLayoutEffect
- Changes are tracked and updated through
updateTransaction
function - Other components subscribe to Redux state changes rather than depending on direct refetch calls
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for components that might be listening to transaction updates
rg -l "onRefetch.*transactions?" --type js --type jsx --type tsx
# Search for any direct dependencies on transaction updates
ast-grep --pattern 'useEffect($$$, [$$$transactions$$$])'
Length of output: 154
Script:
#!/bin/bash
# Search for components that might be listening to transaction updates with correct file extensions
rg -l "onRefetch.*transactions?" --type js
# Search for any direct dependencies on transaction updates in the codebase
rg -A 3 "useEffect.*\[.*transactions.*\]" --type js
# Look for any components that might be subscribing to transaction updates
rg -A 3 "subscribe.*transactions?" --type js
# Search for components using transactions as dependencies
rg -A 3 "transactions.*\bas\b.*dependency" --type js
# Look for components that might be affected by transaction updates
rg -A 3 "transactions.*changed|update.*transactions" --type js
Length of output: 2638
Script:
#!/bin/bash
# Check how transactions state is managed and updated
rg -A 5 "createSlice.*transactions?" --type js
# Look for Redux actions related to transactions
rg -A 5 "dispatch.*transactions?" --type js
# Check for any real-time updates or subscriptions in the app
rg -A 5 "subscribe|listen.*state" --type js
# Look for transaction state management in store
fd "store" --type f --exec rg -A 5 "transactions.*reducer|transactions.*state" {}
Length of output: 6363
packages/loot-core/src/client/queries.ts (3)
70-82
: LGTM! Verify splits option usage.
The function implementation is clean and type-safe. Let's verify the usage of splits: 'grouped'
option.
✅ Verification successful
The splits: 'grouped'
option is correctly used
Based on the codebase analysis, the splits
option is well-tested and consistently used across the application with four valid values:
'grouped'
: Used for transaction queries that need parent-child relationships preserved'none'
: Used for queries that should only return parent transactions'inline'
: Used for queries that should only return non-parent transactions'all'
: Used for exports and schedules to get all transactions
The transactions
function's use of splits: 'grouped'
aligns with other transaction listing components in the codebase and is verified by test coverage in executors.test.ts
.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the usage of splits option across the codebase
# to ensure consistency and identify potential issues.
# Search for splits option usage patterns
rg "options.*splits.*grouped" --type ts
# Search for documentation or tests related to splits option
rg "splits.*grouped" -C 3 --glob "*test*"
Length of output: 1484
Script:
#!/bin/bash
# Let's try a broader search to understand the splits option usage
# Search for any mentions of splits option in the codebase
rg "splits.*['\"]" --type ts -C 3
# Search for query options usage
rg "options\(" --type ts -C 3
# Look for tests or documentation mentioning splits
rg "splits" --glob "*test*" --glob "*.md" -C 3
Length of output: 56148
Line range hint 83-122
: LGTM! Verify amount parsing logic.
The search implementation is comprehensive, handling various search scenarios. Let's verify the amount parsing logic, especially the integer division operation.
✅ Verification successful
Amount parsing logic is well-implemented and consistent
The amount parsing implementation in transactionsSearch
is correct and consistent with the codebase:
currencyToAmount
properly handles various number formats (comma-dot, dot-comma, space-comma, etc.) with comprehensive test coverageamountToInteger
correctly converts amounts by multiplying by 100 and rounding- The query's amount transformations handle both cases correctly:
- Exact decimal amounts using
$abs
andamountToInteger
- Integer amounts using
$abs
and$idiv
by 100 for whole number searches
- Exact decimal amounts using
The implementation is backed by well-tested number formatting utilities and follows the established patterns for amount handling in the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify amount parsing and transformation logic
# to ensure consistency with the rest of the codebase.
# Search for similar amount transformations
rg "\$transform.*\$abs.*\$idiv" --type ts
# Search for amount parsing tests
rg "currencyToAmount|amountToInteger" -C 3 --glob "*test*"
Length of output: 1484
Script:
#!/bin/bash
# Let's search for the implementation of currency conversion functions
rg "function (currencyToAmount|amountToInteger)" -A 5 --type ts
# Also search for any tests related to amount parsing in transaction queries
rg "transactionsSearch.*amount" -B 2 -A 5 --glob "*test*"
# Look for other similar amount transformations in queries
ast-grep --pattern 'amount: { $transform: $_ }'
Length of output: 989
Script:
#!/bin/bash
# Let's look at the number format implementation and tests
rg "getNumberFormat" -A 5 --type ts
# Search for any existing query tests that handle amount transformations
rg "test.*amount.*transform" -B 2 -A 5 --type ts
# Look for similar amount handling in other query functions
ast-grep --pattern '$transform: { $abs: { $idiv: [$_, $_] } }'
Length of output: 9064
Line range hint 31-69
: LGTM! Verify uncategorized transactions filter logic.
The function implementation is well-structured with proper type safety. However, let's verify the complex filter logic for uncategorized transactions.
✅ Verification successful
Based on the evidence gathered from the codebase:
The uncategorized transactions filter logic is correctly implemented
The implementation in accountFilter
is consistent with its usage across the codebase, particularly:
- The function is used in both transaction queries and schedule queries, showing a unified approach to filtering
- The uncategorized filter logic correctly combines multiple conditions:
- Non-off-budget accounts (
offbudget: false
) - No category (
category: null
) - Non-parent transactions (
is_parent: false
) - Handling transfer accounts appropriately
- Non-off-budget accounts (
The complex filter conditions are necessary to properly identify truly uncategorized transactions while excluding transfers to off-budget accounts.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the filter logic for uncategorized transactions matches existing behavior
# by comparing it with other parts of the codebase.
# Search for similar filter patterns for uncategorized transactions
rg -l 'offbudget.*false.*category.*null.*transfer_acct' --type ts
Length of output: 67
Script:
#!/bin/bash
# Let's try a broader search to understand the usage of these fields in queries
# First, let's look for files that might contain similar query patterns
rg -l "offbudget|category|transfer_acct" --type ts
# Then, let's specifically look for query-related files and their content
rg -g "*query*.ts" -g "*filter*.ts" "offbudget|category|transfer_acct" -C 2
Length of output: 11603
Script:
#!/bin/bash
# Let's look for the previous implementation or tests of this filter logic
rg -g "*.test.ts" -g "*.test.tsx" "uncategorized.*transactions|accountFilter" -C 5
# Also search for the usage of this filter in transaction queries
ast-grep --pattern 'accountFilter($$$)'
Length of output: 469
Script:
#!/bin/bash
# Let's examine how the accountFilter is used in schedules.tsx and the surrounding context
rg -g "schedules.tsx" "accountFilter" -B 10 -A 10
# Also look for any filtering logic related to uncategorized transactions in the codebase
rg "is_parent.*false.*category.*null" -C 5
Length of output: 3795
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
21-28
: LGTM: Clean import organization
The imports are well-organized and align with the PR objective of migrating to the new transaction hooks system.
59-62
: LGTM: Improved schedule query management
Good use of useMemo
to memoize the schedule query with explicit dependency on accountId
. The change to use query
prop in SchedulesProvider
improves the API clarity.
Also applies to: 81-81
217-222
: LGTM: Enhanced type safety
Excellent improvement in type definitions. The union type for accountId
makes the valid account types explicit and the use of AccountEntity['name']
ensures type consistency with the model.
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)
2-8
: LGTM! Good improvements to types and imports
The changes improve type safety through readonly arrays and add proper loading state handling. The new hook imports align well with the functional component migration.
Also applies to: 39-40
Line range hint 261-277
: LGTM! Well-optimized filtering logic
The memoized filtering logic is well-structured and properly handles all cases for completed schedules. Good use of readonly type for immutability.
packages/loot-core/src/client/query-helpers.ts (2)
Line range hint 383-417
: LGTM: Well-implemented concurrency handling in _fetchNext.
The method effectively:
- Waits for in-flight requests
- Handles race conditions
- Manages pagination state
- Updates data atomically
235-238
: 🛠️ Refactor suggestion
Enhance error handling in fetchData method.
The current error handling only logs the error. Consider:
- Adding error classification
- Including more context in the error log
- Implementing retry logic for transient errors
} catch (e) {
- console.log('Error fetching data', e);
+ const context = {
+ error: e,
+ query: this._query.serialize(),
+ requestId: reqId
+ };
+ console.error('Error fetching data:', context);
+
+ // Classify and handle different types of errors
+ if (e instanceof NetworkError && this.shouldRetry(e)) {
+ return this.retryFetchData(runQuery);
+ }
+
this.onError(e);
}
Likely invalid or redundant comment.
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (4)
158-159
: LGTM: Good migration to useCategories hook!
The change from prop-based categories to the useCategories hook aligns with React best practices and centralizes category management.
789-789
: LGTM: Clean prop passing to Transaction component.
The categories prop is correctly passed from the useCategories hook to the Transaction component.
587-588
:
Enhance error handling for category processing.
The current implementation directly assigns category_id without validation. This could lead to undefined categories in the imported transactions.
Consider adding validation:
- const category_id = parseCategoryFields(trans, categories);
- trans.category = category_id;
+ try {
+ const category_id = parseCategoryFields(trans, categories);
+ if (category_id || category_id === null) {
+ trans.category = category_id;
+ } else {
+ console.warn(`Invalid category for transaction on ${trans.date}`);
+ }
+ } catch (error) {
+ console.error('Failed to process category:', error);
+ errorMessage = `Failed to process category for transaction on ${trans.date}: ${error.message}`;
+ break;
+ }
Likely invalid or redundant comment.
243-246
:
Fix category matching logic in parseCategoryFields.
The current implementation has a bug where return null
inside forEach doesn't actually return from the function. This could lead to incorrect category matching.
Consider using find or some instead:
function parseCategoryFields(trans, categories) {
- let match = null;
- categories.forEach(category => {
- if (category.id === trans.category) {
- return null;
- }
- if (category.name === trans.category) {
- match = category.id;
- }
- });
- return match;
+ if (!trans.category) return null;
+ const category = categories.find(
+ cat => cat.id === trans.category || cat.name === trans.category
+ );
+ return category?.id || null;
}
Likely invalid or redundant comment.
packages/desktop-client/src/components/table.tsx (2)
819-819
: Consider making isLoading
optional in the type definition
A past review comment already identified this issue, and it's still valid. Making isLoading
a required prop could break existing code that uses SelectedItemsButton
. Since you provide a default value of false
, the type should reflect this optionality.
Line range hint 842-856
: LGTM! Clean implementation of loading state
The replacement of Button
with ButtonWithLoading
and the addition of the isLoading
prop is well implemented. The loading state is properly passed through to the button component.
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)
769-776
: LGTM! Improved schedule loading handling.
The changes properly handle the loading state of schedules by adding an early return, preventing potential null reference errors during the initial load.
reset() { | ||
return q(this.state.table); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
🛠️ Refactor suggestion
Based on the comprehensive analysis of the codebase, I can now provide the final response:
reset()
method should preserve state consistency with other Query methods
The current implementation breaks the pattern established by all other Query methods which carefully preserve state through spreading. Every other method in the Query class maintains state consistency by spreading the existing state when creating new instances.
reset() {
- return q(this.state.table);
+ return new Query({
+ table: this.state.table,
+ tableOptions: this.state.tableOptions
+ });
}
packages/loot-core/src/shared/query.ts:27
: Constructor initializestableOptions
as part of core statepackages/loot-core/src/shared/query.ts:43-133
: All other methods preserve state using spread operator
🔗 Analysis chain
Consider preserving table options in reset method.
The reset
method creates a completely new query instance, which loses all table options. Consider whether preserving the table options would be more appropriate for a reset operation.
reset() {
- return q(this.state.table);
+ return new Query({
+ table: this.state.table,
+ tableOptions: this.state.tableOptions
+ });
}
Let me analyze the results and generate additional verification scripts to understand the full context of how tableOptions
are used in query operations.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if tableOptions are used consistently across the codebase
rg -A 3 "tableOptions" --type ts
Length of output: 3418
Script:
#!/bin/bash
# Check for Query class constructor and methods that handle state
ast-grep --pattern 'class Query {
$$$
constructor($_) {
$$$
}
$$$
}'
# Check for other methods that create new Query instances
rg -A 3 "new Query" --type ts
Length of output: 16566
export function useTransactions({ | ||
query, | ||
options = { pageCount: 50 }, | ||
}: UseTransactionsProps): UseTransactionsResult { | ||
const [isLoading, setIsLoading] = useState(false); | ||
const [error, setError] = useState<Error | undefined>(undefined); | ||
const [transactions, setTransactions] = useState< | ||
ReadonlyArray<TransactionEntity> | ||
>([]); | ||
|
||
const pagedQueryRef = useRef<PagedQuery<TransactionEntity> | null>(null); | ||
|
||
// We don't want to re-render if options changes. | ||
// Putting options in a ref will prevent that and | ||
// allow us to use the latest options on next render. | ||
const optionsRef = useRef(options); | ||
optionsRef.current = options; | ||
|
||
useEffect(() => { | ||
let isUnmounted = false; | ||
|
||
setError(undefined); | ||
|
||
if (!query) { | ||
return; | ||
} | ||
|
||
function onError(error: Error) { | ||
if (!isUnmounted) { | ||
setError(error); | ||
setIsLoading(false); | ||
} | ||
} | ||
|
||
if (query.state.table !== 'transactions') { | ||
onError(new Error('Query must be a transactions query.')); | ||
return; | ||
} | ||
|
||
setIsLoading(true); | ||
|
||
pagedQueryRef.current = pagedQuery<TransactionEntity>(query, { | ||
onData: data => { | ||
if (!isUnmounted) { | ||
setTransactions(data); | ||
setIsLoading(false); | ||
} | ||
}, | ||
onError, | ||
options: { pageCount: optionsRef.current.pageCount }, | ||
}); | ||
|
||
return () => { | ||
isUnmounted = true; | ||
pagedQueryRef.current?.unsubscribe(); | ||
}; | ||
}, [query]); | ||
|
||
return { | ||
transactions, | ||
isLoading, | ||
error, | ||
reload: pagedQueryRef.current?.run, | ||
loadMore: pagedQueryRef.current?.fetchNext, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding TypeScript type guards for reload and loadMore functions.
While the past review comments suggest providing no-op functions, a TypeScript-first approach would be to properly type these functions to make their nullable nature explicit to consumers.
Apply this diff to improve type safety:
type UseTransactionsResult = {
transactions: ReadonlyArray<TransactionEntity>;
isLoading?: boolean;
error?: Error;
- reload?: () => void;
- loadMore?: () => void;
+ reload: (() => void) | null;
+ loadMore: (() => void) | null;
};
// ...
return {
transactions,
isLoading,
error,
- reload: pagedQueryRef.current?.run,
- loadMore: pagedQueryRef.current?.fetchNext,
+ reload: pagedQueryRef.current?.run ?? null,
+ loadMore: pagedQueryRef.current?.fetchNext ?? null,
};
Committable suggestion skipped: line range outside the PR's diff.
export function useTransactionsSearch({ | ||
updateQuery, | ||
resetQuery, | ||
dateFormat, | ||
delayMs = 150, | ||
}: UseTransactionsSearchProps): UseTransactionsSearchResult { | ||
const [isSearching, setIsSearching] = useState(false); | ||
|
||
const updateSearchQuery = useMemo( | ||
() => | ||
debounce((searchText: string) => { | ||
if (searchText === '') { | ||
resetQuery(); | ||
setIsSearching(false); | ||
} else if (searchText) { | ||
updateQuery(previousQuery => | ||
queries.transactionsSearch(previousQuery, searchText, dateFormat), | ||
); | ||
setIsSearching(true); | ||
} | ||
}, delayMs), | ||
[dateFormat, delayMs, resetQuery, updateQuery], | ||
); | ||
|
||
useEffect(() => { | ||
return () => updateSearchQuery.cancel(); | ||
}, [updateSearchQuery]); | ||
|
||
return { | ||
isSearching, | ||
search: updateSearchQuery, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding a loading state for initial search setup.
The isSearching
state only reflects the search in progress, but not the initial setup of the search functionality. This could lead to a poor user experience if there's a delay in setting up the search capabilities.
Apply this diff to add initial loading state:
type UseTransactionsSearchResult = {
isSearching: boolean;
+ isReady: boolean;
search: (searchText: string) => void;
};
export function useTransactionsSearch({
updateQuery,
resetQuery,
dateFormat,
delayMs = 150,
}: UseTransactionsSearchProps): UseTransactionsSearchResult {
const [isSearching, setIsSearching] = useState(false);
+ const [isReady, setIsReady] = useState(false);
+
+ useEffect(() => {
+ setIsReady(true);
+ return () => setIsReady(false);
+ }, []);
// ... rest of the code ...
return {
isSearching,
+ isReady,
search: updateSearchQuery,
};
}
Committable suggestion skipped: line range outside the PR's diff.
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, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing baseTransactionsQuery result
While the callback is memoized, the query object it creates is recreated on each call. Consider memoizing the result:
- const baseTransactionsQuery = useCallback(
+ const baseTransactionsQuery = useMemo(
- () =>
- queries.transactions(accountId).options({ splits: 'none' }).select('*'),
+ () => queries.transactions(accountId).options({ splits: 'none' }).select('*'),
[accountId],
);
const [transactionsQuery, setTransactionsQuery] = useState<Query>(
- baseTransactionsQuery(),
+ baseTransactionsQuery,
);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const baseTransactionsQuery = useCallback( | |
() => | |
!isSearching ? prependTransactions.concat(transactions) : transactions, | |
[isSearching, prependTransactions, transactions], | |
queries.transactions(accountId).options({ splits: 'none' }).select('*'), | |
[accountId], | |
); | |
const [transactionsQuery, setTransactionsQuery] = useState<Query>( | |
baseTransactionsQuery(), | |
); | |
const { | |
transactions, | |
isLoading, | |
reload: reloadTransactions, | |
loadMore: loadMoreTransactions, | |
} = useTransactions({ | |
query: transactionsQuery, | |
}); | |
const baseTransactionsQuery = useMemo( | |
() => queries.transactions(accountId).options({ splits: 'none' }).select('*'), | |
[accountId], | |
); | |
const [transactionsQuery, setTransactionsQuery] = useState<Query>( | |
baseTransactionsQuery, | |
); | |
const { | |
transactions, | |
isLoading, | |
reload: reloadTransactions, | |
loadMore: loadMoreTransactions, | |
} = useTransactions({ | |
query: transactionsQuery, | |
}); |
const renderSchedule = useCallback( | ||
({ schedule }: { schedule: ScheduleEntity }) => { | ||
const status = statuses.get(schedule.id); | ||
return ( | ||
<Row | ||
height={ROW_HEIGHT} | ||
inset={15} | ||
onClick={() => onSelect(schedule.id)} | ||
style={{ | ||
cursor: 'pointer', | ||
backgroundColor: 'transparent', | ||
backgroundColor: theme.tableBackground, | ||
color: theme.tableText, | ||
':hover': { backgroundColor: theme.tableRowBackgroundHover }, | ||
}} | ||
onClick={() => setShowCompleted(true)} | ||
> | ||
<Field | ||
width="flex" | ||
style={{ | ||
fontStyle: 'italic', | ||
textAlign: 'center', | ||
color: theme.tableText, | ||
}} | ||
> | ||
<Trans>Show completed schedules</Trans> | ||
<Field width="flex" name="name"> | ||
<Text | ||
style={ | ||
!schedule.name | ||
? { color: theme.buttonNormalDisabledText } | ||
: undefined | ||
} | ||
title={schedule.name ? schedule.name : ''} | ||
> | ||
{schedule.name ? schedule.name : t('None')} | ||
</Text> | ||
</Field> | ||
<Field width="flex" name="payee"> | ||
<DisplayId type="payees" id={schedule._payee} /> | ||
</Field> | ||
<Field width="flex" name="account"> | ||
<DisplayId type="accounts" id={schedule._account} /> | ||
</Field> | ||
<Field width={110} name="date"> | ||
{schedule.next_date | ||
? monthUtilFormat(schedule.next_date, dateFormat) | ||
: null} | ||
</Field> | ||
<Field width={120} name="status" style={{ alignItems: 'flex-start' }}> | ||
{status && <StatusBadge status={status} />} | ||
</Field> | ||
<ScheduleAmountCell | ||
amount={schedule._amount} | ||
op={schedule._amountOp} | ||
/> | ||
{!minimal && ( | ||
<Field width={80} style={{ textAlign: 'center' }}> | ||
{schedule._date && schedule._date.frequency && ( | ||
<SvgCheck style={{ width: 13, height: 13 }} /> | ||
)} | ||
</Field> | ||
)} | ||
{!minimal && ( | ||
<Field width={40} name="actions"> | ||
<OverflowMenu | ||
schedule={schedule} | ||
status={status} | ||
onAction={onAction} | ||
/> | ||
</Field> | ||
)} | ||
</Row> | ||
); | ||
} | ||
return renderSchedule({ schedule: item as ScheduleEntity }); | ||
} | ||
}, | ||
[statuses, dateFormat, onAction, minimal, onSelect], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing style objects for better performance
The inline style objects in the Row and Text components will be recreated on every render. Consider extracting and memoizing them:
const rowStyle = useMemo(() => ({
cursor: 'pointer',
backgroundColor: theme.tableBackground,
color: theme.tableText,
':hover': { backgroundColor: theme.tableRowBackgroundHover }
}), [theme]);
const getNameStyle = useCallback((hasName: boolean) =>
!hasName ? { color: theme.buttonNormalDisabledText } : undefined
, [theme]);
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
b669447
to
c720427
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Outside diff range and nitpick comments (3)
packages/desktop-client/src/components/schedules/ScheduleLink.tsx (1)
41-49
: LGTM: Well-structured query and data fetchingGood use of
useMemo
for query optimization and proper extraction of loading state.Consider adding a type annotation for
schedulesQuery
to enhance type safety:- const schedulesQuery = useMemo( + const schedulesQuery: Query<'schedules'> = useMemo( () => q('schedules').filter({ completed: false }).select('*'), [], );packages/desktop-client/src/components/accounts/Header.tsx (2)
22-22
: Remove commented-out importThe commented-out import for
AnimatedLoading
should be removed to maintain clean code.-// import { AnimatedLoading } from '../../icons/AnimatedLoading';
722-737
: Remove commented out code blockThe commented out code block has been replaced with a simpler implementation. Remove it to maintain code cleanliness.
- // ...(canShowBalances - // ? [ - // { - // name: 'toggle-balance', - // text: showBalances - // ? t('Hide running balance') - // : t('Show running balance'), - // } as const, - // ] - // : []),[ { name: 'toggle-balance', text: showBalances ? t('Hide running balance') : t('Show running balance'), },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (7)
packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--321fd-ed-amount-opens-the-budget-summary-menu-modal-2-chromium-linux.png
is excluded by!**/*.png
packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--4bb70-ed-amount-opens-the-budget-summary-menu-modal-1-chromium-linux.png
is excluded by!**/*.png
packages/desktop-client/e2e/budget.mobile.test.js-snapshots/Mobile-Budget-Envelope-checks-that-clicking--94a85-ed-amount-opens-the-budget-summary-menu-modal-3-chromium-linux.png
is excluded by!**/*.png
packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png
is excluded by!**/*.png
packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png
is excluded by!**/*.png
packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-3-chromium-linux.png
is excluded by!**/*.png
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (14)
packages/desktop-client/src/components/accounts/Balance.jsx
(9 hunks)packages/desktop-client/src/components/accounts/Header.tsx
(13 hunks)packages/desktop-client/src/components/accounts/Reconcile.tsx
(1 hunks)packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx
(7 hunks)packages/desktop-client/src/components/schedules/ScheduleLink.tsx
(3 hunks)packages/desktop-client/src/components/schedules/SchedulesTable.tsx
(6 hunks)packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
(2 hunks)packages/desktop-client/src/components/table.tsx
(4 hunks)packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
(3 hunks)packages/desktop-client/src/components/transactions/TransactionList.jsx
(2 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.jsx
(4 hunks)packages/desktop-client/src/hooks/usePreviewTransactions.ts
(1 hunks)packages/desktop-client/src/hooks/useSelected.tsx
(2 hunks)packages/loot-core/src/shared/transactions.ts
(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/table.tsx
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/accounts/Header.tsx (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/accounts/Account.tsx:655-665
Timestamp: 2024-10-24T17:05:41.415Z
Learning: The Account component in 'packages/desktop-client/src/components/accounts/Account.tsx' is being rewritten in a separate PR.
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (19)
packages/desktop-client/src/hooks/usePreviewTransactions.ts (3)
9-14
: LGTM: Clean type imports and hook organization.
The addition of proper type imports and the usePrevious
hook import improves type safety and code organization.
27-32
: LGTM: Improved loading state handling.
The addition of the loading state check prevents processing incomplete data, making the hook more robust.
16-20
: Verify migration status of deprecated hook.
While the deprecation notice is clear, let's verify the migration status to ensure we're not removing this hook prematurely.
✅ Verification successful
Migration is in progress but incomplete
The verification shows:
- The new hook is properly implemented in
loot-core/src/client/data-hooks/transactions.ts
- The new hook is already being used in:
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx
packages/desktop-client/src/components/accounts/Account.tsx
- However, the old deprecated hook still exists and appears to be unused (ESLint warning confirms this)
You can safely remove the deprecated hook file as the migration is complete.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check migration status of usePreviewTransactions hook
echo "Checking current usage of deprecated hook:"
rg "usePreviewTransactions.*from.*'.*desktop-client/src/hooks/usePreviewTransactions'" --type tsx --type ts
echo "Checking usage of new hook:"
rg "usePreviewTransactions.*from.*'loot-core/client/data-hooks/transactions'" --type tsx --type ts
Length of output: 438
Script:
#!/bin/bash
# Description: Check migration status of usePreviewTransactions hook with corrected file types
echo "Checking current usage of deprecated hook:"
rg "usePreviewTransactions.*from.*'.*desktop-client/src/hooks/usePreviewTransactions'"
echo -e "\nChecking usage of new hook:"
rg "usePreviewTransactions.*from.*'loot-core/client/data-hooks/transactions'"
echo -e "\nChecking if new hook exists:"
fd "transactions.ts" --type f | grep "data-hooks"
Length of output: 524
Script:
#!/bin/bash
# Let's verify the actual implementation and usage
# Check if the new hook is implemented in the target file
echo "Checking new hook implementation:"
cat packages/loot-core/src/client/data-hooks/transactions.ts
# Search for any usage of usePreviewTransactions without path constraints
echo -e "\nChecking all usages of usePreviewTransactions:"
rg "usePreviewTransactions" -A 2
Length of output: 8558
packages/desktop-client/src/components/schedules/ScheduleLink.tsx (2)
2-2
: LGTM: Import changes align with functional component migration
The added imports for React hooks and query utilities support the transition to a functional component architecture.
Also applies to: 7-7
135-135
: LGTM: Proper loading state handling
Good addition of the isLoading
prop to SchedulesTable
for better user experience during data fetching.
packages/desktop-client/src/components/accounts/Balance.jsx (5)
72-76
: Good addition of loading state handling!
The addition of loading state check prevents rendering with incomplete data, improving the component's reliability.
117-124
: LGTM! Good improvements to FilteredBalance component.
The changes improve the component by:
- Using consistent naming with filteredBalance
- Adding a fallback to 0 for undefined values, preventing NaN issues
158-165
: Well-implemented memoization for balance binding!
Good use of useMemo with appropriate dependencies to prevent unnecessary recalculations of the balance binding.
176-177
: Good upgrade to ButtonWithLoading!
The switch to ButtonWithLoading with proper loading state handling improves the user experience by providing visual feedback during data loading.
Line range hint 90-94
: Verify the updated schedule balance calculation logic.
The balance calculation has been inverted based on account ownership. Please verify that:
- Adding to scheduleBalance when
accountId !== s._account
is correct - Subtracting from scheduleBalance when
accountId === s._account
is correct
This change could significantly impact balance calculations across the application.
✅ Verification successful
Based on the comprehensive context gathered, I can now generate the final response:
The schedule balance calculation logic is correct
The implementation correctly:
- Adds to scheduleBalance when
accountId !== s._account
because it represents money coming into this account from another account - Subtracts from scheduleBalance when
accountId === s._account
because it represents money going out from this account
This is verified by:
- The consistent usage of
getScheduledAmount
across the codebase for schedule amount calculations - The same logic being used in
TransactionsTable.jsx
where scheduled transaction amounts are inverted based on account ownership - The implementation in
Account.tsx
where balance calculations follow the same principle of adding/subtracting based on the transaction direction
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for other instances of schedule balance calculations
rg -A 5 "scheduleBalance.*[+-]=" .
# Search for tests related to schedule balance
rg -l "test.*schedule.*balance" .
Length of output: 743
Script:
#!/bin/bash
# Let's understand the broader context of this balance calculation
# 1. Get the full Balance component implementation
ast-grep --pattern 'function Balance($_) {
$$$
}'
# 2. Look for any related schedule amount calculations
rg -B 5 -A 5 "getScheduledAmount" .
# 3. Look for any tests or documentation about scheduled transactions
fd -e md -e test.js -e test.jsx . | xargs rg -l "schedule.*transaction|scheduled.*amount"
Length of output: 18260
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)
39-40
: LGTM! Type improvements enhance type safety.
The addition of isLoading
prop and making schedules
readonly improves type safety and immutability guarantees.
278-343
: LGTM! Proper useCallback implementation.
The renderSchedule function is correctly memoized with appropriate dependencies.
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/desktop-client/src/components/accounts/Header.tsx (1)
56-76
: LGTM! Props refactoring improves type safety
The changes to the props improve type safety and clarity:
- Making
transactions
readonly prevents accidental mutations - Renaming
workingHard
toisLoading
is more semantic - New props properly type the filtered state
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (3)
158-159
: LGTM: Clean hook integration.
The migration to use the useCategories
hook is clean and follows React hooks best practices.
789-789
: LGTM: Proper prop passing.
The categories prop is correctly passed to the Transaction component.
313-313
: 🛠️ Refactor suggestion
Verify dependency array completeness.
The getImportPreview
function's dependency array includes categories
, but it might be better to depend on the memoized categoryMap
instead to prevent unnecessary re-renders.
Consider updating the dependency array after implementing the memoized category map:
-[accountId, categories, clearOnImport, importPreviewTransactions],
+[accountId, categoryMap, clearOnImport, importPreviewTransactions],
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)
769-776
: Good implementation of loading state handling!
The addition of loading state handling for schedules is a good practice that prevents potential rendering issues and null reference errors. The early return pattern when loading is a clean way to handle the loading state.
1994-1994
: LGTM: Loading state properly passed to Table component.
The loading state is correctly propagated to the Table component, maintaining consistency in loading state handling throughout the component hierarchy.
1765-1765
: Verify loading state propagation.
The isLoading
prop has been added to the component parameters, but we should ensure it's properly handled throughout the component lifecycle.
Also applies to: 1775-1775
✅ Verification successful
Let me gather more information about how the isLoading
prop is used in the TransactionsTable component.
Loading state is properly propagated and utilized
The isLoading
prop is correctly handled in the TransactionsTable component:
- It's properly passed down to the underlying table component via the
loading
prop - It's also used in the schedules logic with appropriate conditional rendering
- The implementation follows the expected patterns for loading state management
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if isLoading prop is properly used in child components
# Test: Search for isLoading prop usage in the codebase
rg -A 3 'isLoading.*prop[s]?'
Length of output: 373
Script:
#!/bin/bash
# Check the full implementation of TransactionsTable component
ast-grep --pattern 'const TransactionsTable = ({ $$$, isLoading, $$$ }) => {
$$$
}'
# Also search for any loading state usage in the file
rg -A 5 'isLoading' packages/desktop-client/src/components/transactions/TransactionsTable.jsx
Length of output: 691
Promise.all( | ||
baseTrans.map(transaction => send('rules-run', { transaction })), | ||
).then(newTrans => { | ||
const withDefaults = newTrans.map(t => ({ | ||
...t, | ||
category: scheduleData.statuses.get(t.schedule), | ||
schedule: t.schedule, | ||
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({ | ||
...st, | ||
id: 'preview/' + st.id, | ||
schedule: t.schedule, | ||
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({ | ||
...st, | ||
id: 'preview/' + st.id, | ||
schedule: t.schedule, | ||
})), | ||
})); | ||
setPreviewTransactions(ungroupTransactions(withDefaults)); | ||
if (collapseTransactions) { | ||
collapseTransactions(withDefaults.map(t => t.id)); | ||
} | ||
}); | ||
} | ||
|
||
return previewTransactions; | ||
})), | ||
})); | ||
setPreviewTransactions(ungroupTransactions(withDefaults)); | ||
if (collapseTransactions) { | ||
collapseTransactions(withDefaults.map(t => t.id)); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for Promise.all.
The Promise chain lacks error handling, which could lead to unhandled rejections if rule application fails.
Add error handling:
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
- ).then(newTrans => {
+ ).then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
+ }).catch(error => {
+ console.error('Error applying rules to transactions:', error);
+ setPreviewTransactions([]); // Reset to empty state on error
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
Promise.all( | |
baseTrans.map(transaction => send('rules-run', { transaction })), | |
).then(newTrans => { | |
const withDefaults = newTrans.map(t => ({ | |
...t, | |
category: scheduleData.statuses.get(t.schedule), | |
schedule: t.schedule, | |
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({ | |
...st, | |
id: 'preview/' + st.id, | |
schedule: t.schedule, | |
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({ | |
...st, | |
id: 'preview/' + st.id, | |
schedule: t.schedule, | |
})), | |
})); | |
setPreviewTransactions(ungroupTransactions(withDefaults)); | |
if (collapseTransactions) { | |
collapseTransactions(withDefaults.map(t => t.id)); | |
} | |
}); | |
} | |
return previewTransactions; | |
})), | |
})); | |
setPreviewTransactions(ungroupTransactions(withDefaults)); | |
if (collapseTransactions) { | |
collapseTransactions(withDefaults.map(t => t.id)); | |
} | |
}); | |
Promise.all( | |
baseTrans.map(transaction => send('rules-run', { transaction })), | |
).then(newTrans => { | |
const withDefaults = newTrans.map(t => ({ | |
...t, | |
category: scheduleData.statuses.get(t.schedule), | |
schedule: t.schedule, | |
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({ | |
...st, | |
id: 'preview/' + st.id, | |
schedule: t.schedule, | |
})), | |
})); | |
setPreviewTransactions(ungroupTransactions(withDefaults)); | |
if (collapseTransactions) { | |
collapseTransactions(withDefaults.map(t => t.id)); | |
} | |
}).catch(error => { | |
console.error('Error applying rules to transactions:', error); | |
setPreviewTransactions([]); // Reset to empty state on error | |
}); |
const onSelect = useCallback( | ||
async (scheduleId: string) => { | ||
if (ids?.length > 0) { | ||
await send('transactions-batch-update', { | ||
updated: ids.map(id => ({ id, schedule: scheduleId })), | ||
}); | ||
onScheduleLinked?.(schedules.find(s => s.id === scheduleId)); | ||
} | ||
}, | ||
[ids, onScheduleLinked, schedules], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for batch update operation
While the callback implementation is good, consider adding error handling for the batch update operation to improve reliability.
const onSelect = useCallback(
async (scheduleId: string) => {
if (ids?.length > 0) {
+ try {
await send('transactions-batch-update', {
updated: ids.map(id => ({ id, schedule: scheduleId })),
});
onScheduleLinked?.(schedules.find(s => s.id === scheduleId));
+ } catch (error) {
+ console.error('Failed to update transactions:', error);
+ // Consider showing a user-friendly error message
+ }
}
},
[ids, onScheduleLinked, schedules],
);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const onSelect = useCallback( | |
async (scheduleId: string) => { | |
if (ids?.length > 0) { | |
await send('transactions-batch-update', { | |
updated: ids.map(id => ({ id, schedule: scheduleId })), | |
}); | |
onScheduleLinked?.(schedules.find(s => s.id === scheduleId)); | |
} | |
}, | |
[ids, onScheduleLinked, schedules], | |
); | |
const onSelect = useCallback( | |
async (scheduleId: string) => { | |
if (ids?.length > 0) { | |
try { | |
await send('transactions-batch-update', { | |
updated: ids.map(id => ({ id, schedule: scheduleId })), | |
}); | |
onScheduleLinked?.(schedules.find(s => s.id === scheduleId)); | |
} catch (error) { | |
console.error('Failed to update transactions:', error); | |
// Consider showing a user-friendly error message | |
} | |
} | |
}, | |
[ids, onScheduleLinked, schedules], | |
); |
@@ -250,7 +258,7 @@ export function SchedulesTable({ | |||
}); | |||
}, [payees, accounts, schedules, filter, statuses]); | |||
|
|||
const items: SchedulesTableItem[] = useMemo(() => { | |||
const items: readonly SchedulesTableItem[] = useMemo(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider removing type assertion in Table props
While making items
readonly is good, there's a type assertion when passing items to Table component that could be improved.
Consider updating the Table component's type definition to accept readonly arrays instead of using type assertion:
- items={items as ScheduleEntity[]}
+ items={items}
This would require updating the Table component's props type to:
interface TableProps<T> {
items: readonly T[];
// ... other props
}
Also applies to: 410-410
@@ -383,6 +403,7 @@ export function SchedulesTable({ | |||
{!minimal && <Field width={40} />} | |||
</TableHeader> | |||
<Table | |||
loading={isLoading} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider enhancing loading state UI feedback
While the loading state is correctly implemented, consider adding visual feedback for better user experience.
Consider implementing one of these loading state patterns:
- Skeleton loading UI that mimics the table structure
- Loading spinner with partially faded content
- Loading progress bar at the top of the table
Would you like me to provide example code for any of these approaches?
<SelectedTransactionsButton | ||
isLoading={isLoading} | ||
getTransaction={id => transactions.find(t => t.id === id)} | ||
onShow={onShowTransactions} | ||
onDuplicate={onBatchDuplicate} | ||
onDelete={onBatchDelete} | ||
onEdit={onBatchEdit} | ||
onLinkSchedule={onBatchLinkSchedule} | ||
onUnlinkSchedule={onBatchUnlinkSchedule} | ||
onCreateRule={onCreateRule} | ||
onSetTransfer={onSetTransfer} | ||
onScheduleAction={onScheduleAction} | ||
showMakeTransfer={showMakeTransfer} | ||
onMakeAsSplitTransaction={onMakeAsSplitTransaction} | ||
onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize transaction lookup performance
The getTransaction
implementation using array.find
could be inefficient for large transaction lists. Consider using a Map for O(1) lookup.
+const transactionsMap = useMemo(
+ () => new Map(transactions.map(t => [t.id, t])),
+ [transactions]
+);
<SelectedTransactionsButton
isLoading={isLoading}
- getTransaction={id => transactions.find(t => t.id === id)}
+ getTransaction={id => transactionsMap.get(id)}
// ... other props
/>
Committable suggestion skipped: line range outside the PR's diff.
onChange={search => { | ||
setSearch(search); | ||
onSearch?.(search); | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing search handler
The search handler could be memoized to prevent unnecessary rerenders.
+const handleSearch = useCallback((search: string) => {
+ setSearch(search);
+ onSearch?.(search);
+}, [onSearch]);
-onChange={search => {
- setSearch(search);
- onSearch?.(search);
-}}
+onChange={handleSearch}
Committable suggestion skipped: line range outside the PR's diff.
const category_id = parseCategoryFields(trans, categories); | ||
if (category_id != null) { | ||
trans.category = category_id; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing category parsing for better performance.
The parseCategoryFields
function performs a linear search through categories on every transaction. This could be inefficient for large datasets.
Consider memoizing the category lookup:
+const categoryMap = useMemo(
+ () => new Map(categories.map(cat => [cat.name, cat.id])),
+ [categories]
+);
-function parseCategoryFields(trans, categories) {
+function parseCategoryFields(trans) {
if (!trans.category) return null;
- let match = null;
- categories.forEach(category => {
- if (category.id === trans.category) {
- return null;
- }
- if (category.name === trans.category) {
- match = category.id;
- }
- });
- return match;
+ return categoryMap.get(trans.category) || null;
}
Also applies to: 587-588
existing_trx.category = categories.find( | ||
cat => cat.id === existing_trx.category, | ||
)?.name; | ||
// add parent transaction attribute to mimic behaviour |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize category lookup in existing transaction handling.
The current implementation uses find
for each category lookup, which could be inefficient.
Use the same memoized category map:
-existing_trx.category = categories.find(
- cat => cat.id === existing_trx.category
-)?.name;
+const category = categoryMap.get(existing_trx.category);
+existing_trx.category = category ? category.name : null;
Committable suggestion skipped: line range outside the PR's diff.
c720427
to
12f571f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (4)
packages/desktop-client/src/components/accounts/Balance.jsx (2)
157-165
: Remove commented-out code.The commented-out code on line 157 should be removed as it's no longer needed and can cause confusion.
- // const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' });
217-223
: Remove commented-out prop.The commented-out
transactionsQuery
prop should be removed.<MoreBalances accountId={accountId} - // transactionsQuery={transactionsQuery} balanceQuery={balanceQuery} />
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)
769-776
: Consider showing a loading indicator instead of returning null.The loading state handling is correct and prevents potential errors, but the user experience could be improved.
Consider replacing the null return with a loading indicator:
if (isLoading) { - return null; + return <LoadingSpinner size="small" />; }Also, consider using optional chaining for safer schedule lookup:
- const schedule = scheduleId ? schedules.find(s => s.id === scheduleId) : null; + const schedule = scheduleId ? schedules?.find(s => s.id === scheduleId) : null;
Line range hint
1765-1775
: Document the isLoading prop type.The loading state handling is correctly implemented, but the new prop should be documented for better maintainability.
Consider adding prop types documentation at the top of the component:
+ /** + * @typedef {Object} TransactionTableInnerProps + * @property {boolean} isLoading - Indicates whether transaction data is being loaded + */ /** * @param {TransactionTableInnerProps} props */ function TransactionTableInner({ isLoading, tableNavigator, ...Also applies to: 1994-1994
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (14)
packages/desktop-client/src/components/accounts/Balance.jsx
(9 hunks)packages/desktop-client/src/components/accounts/Header.tsx
(13 hunks)packages/desktop-client/src/components/accounts/Reconcile.tsx
(1 hunks)packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx
(7 hunks)packages/desktop-client/src/components/schedules/ScheduleLink.tsx
(3 hunks)packages/desktop-client/src/components/schedules/SchedulesTable.tsx
(6 hunks)packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
(2 hunks)packages/desktop-client/src/components/table.tsx
(4 hunks)packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
(3 hunks)packages/desktop-client/src/components/transactions/TransactionList.jsx
(2 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.jsx
(4 hunks)packages/desktop-client/src/hooks/usePreviewTransactions.ts
(1 hunks)packages/desktop-client/src/hooks/useSelected.tsx
(2 hunks)packages/loot-core/src/shared/transactions.ts
(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
- packages/desktop-client/src/components/accounts/Header.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/shared/transactions.ts
🧰 Additional context used
🪛 Biome
packages/desktop-client/src/components/schedules/SchedulesTable.tsx
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (16)
packages/desktop-client/src/components/accounts/Balance.jsx (4)
Line range hint 1-14
: LGTM! Import cleanup and component updates look good.
The imports have been properly cleaned up, and the switch to ButtonWithLoading
aligns with the loading state management improvements.
Line range hint 90-94
: LGTM! Improved schedule balance calculation logic.
The account comparison and balance calculation now correctly handle transactions between different accounts.
117-123
: LGTM! Improved null handling in FilteredBalance.
The addition of the || 0
fallback ensures the balance always displays a valid number.
71-77
:
Initialize scheduleBalance to prevent NaN issues.
The variable scheduleBalance
is still initialized to null
but is used in arithmetic operations. This can lead to NaN results.
Apply this fix:
- let scheduleBalance = null;
+ let scheduleBalance = 0;
Likely invalid or redundant comment.
packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4)
2-8
: LGTM! Good type safety improvements.
The changes improve type safety by:
- Making the schedules array readonly to enforce immutability
- Adding proper typing for the loading state
Also applies to: 39-40
261-261
: LGTM! Consistent immutability.
The readonly type for items array maintains immutability consistency with the schedules prop.
278-343
: LGTM! Good performance optimization with useCallback.
The renderSchedule function is properly memoized with a complete dependencies array.
🧰 Tools
🪛 Biome
[error] 325-325: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
345-375
: LGTM! Good performance optimization with useCallback.
The renderItem function is properly memoized with the correct dependency.
Also applies to: 406-406
packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx (6)
8-8
: LGTM: Clean integration of the useCategories
hook.
The hook integration is clean and follows React best practices by destructuring the list
property from the hook's return value.
Also applies to: 158-159
313-313
: LGTM: Proper dependency array update.
The dependency array correctly includes categories
for the getImportPreview
callback.
789-789
: LGTM: Proper prop passing to Transaction component.
The categories prop is correctly passed to the Transaction component.
298-301
: 🛠️ Refactor suggestion
Optimize category name lookup for existing transactions.
Similar to the previous issue, the find
operation here could be optimized.
Use a Map for efficient lookups:
+const categoryNameMap = new Map(categories.map(cat => [cat.id, cat.name]));
-existing_trx.category = categories.find(
- cat => cat.id === existing_trx.category
-)?.name;
+existing_trx.category = categoryNameMap.get(existing_trx.category) || null;
Likely invalid or redundant comment.
243-246
: 🛠️ Refactor suggestion
Optimize category lookup performance.
The current implementation of parseCategoryFields
performs a linear search through categories for each transaction. This could be inefficient for large datasets.
Consider using a Map for O(1) lookup:
+const categoryMap = new Map(categories.map(cat => [cat.name, cat.id]));
function parseCategoryFields(trans, categories) {
- let match = null;
- categories.forEach(category => {
- if (category.id === trans.category) {
- return null;
- }
- if (category.name === trans.category) {
- match = category.id;
- }
- });
- return match;
+ if (!trans.category) return null;
+ return categoryMap.get(trans.category) || null;
}
Likely invalid or redundant comment.
587-588
: 🛠️ Refactor suggestion
Consider handling edge cases in category processing.
The category processing during import lacks validation for edge cases.
Add validation and error handling:
- const category_id = parseCategoryFields(trans, categories);
- trans.category = category_id;
+ try {
+ const category_id = parseCategoryFields(trans, categories);
+ trans.category = category_id;
+ } catch (error) {
+ console.warn(`Failed to process category for transaction on ${trans.date}:`, error);
+ // Allow import to continue without category
+ trans.category = null;
+ }
Likely invalid or redundant comment.
packages/desktop-client/src/components/table.tsx (2)
827-827
: LGTM: Loading state implementation looks good
The implementation correctly:
- Provides a default value of
false
for theisLoading
prop - Uses the new
ButtonWithLoading
component to show loading state - Preserves existing functionality while adding loading indicator support
Also applies to: 842-856
819-819
:
Make isLoading
optional in SelectedItemsButtonProps
The isLoading
property should be optional since it has a default value and to maintain backward compatibility.
Apply this diff:
- isLoading: boolean;
+ isLoading?: boolean;
Likely invalid or redundant comment.
… used in useTransactions
12f571f
to
6898f76
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (11)
packages/desktop-client/src/components/accounts/Balance.jsx (2)
157-157
: Remove commented out code.These commented lines about
transactionsQuery
should be removed as they're no longer needed and can cause confusion.- // const balanceQuery = transactionsQuery?.calculate({ $sum: '$amount' }); - // transactionsQuery={transactionsQuery}Also applies to: 220-220
Line range hint
1-231
: Well-structured component hierarchy with clear responsibilities.The component hierarchy is well-organized with clear separation of concerns:
DetailedBalance
: Handles the visual presentation of balancesSelectedBalance
: Manages selected transaction calculationsFilteredBalance
: Displays filtered balance informationMoreBalances
: Shows cleared/uncleared totalsBalances
: Orchestrates the overall balance displayThe migration to functional components and the introduction of proper prop types has improved the maintainability of the code.
Consider splitting these components into separate files if they grow more complex in the future, as they each have distinct responsibilities.
packages/loot-core/src/shared/transactions.ts (6)
163-163
: Consider strengthening return type safety.The function implementation looks good, but consider making the return type more specific by using a discriminated union type for the
newTransaction
property to better handle the_deleted
flag case.type TransactionResult = | { _deleted: true } & TransactionEntity | (TransactionEntity | TransactionEntityWithError); interface ReplaceTransactionsResult { data: TransactionEntity[]; newTransaction: TransactionResult | null; diff: ReturnType<typeof diffItems<TransactionEntity>>; }
248-248
: Consider making property updates more generic.The function correctly handles readonly arrays, but the payee update logic could be made more generic to handle other properties that might need similar parent-child synchronization.
// Consider refactoring to something like this: const syncedProps = ['payee', 'date'] as const; const child = { ...t, ...Object.fromEntries( syncedProps .filter(prop => t[prop] === trans[prop]) .map(prop => [prop, transaction[prop]]) ) };
281-281
: Consider enhancing error handling.While the function correctly handles readonly arrays, consider adding validation for edge cases:
- When deleting the last child transaction
- When the parent transaction is not found
if (!trans) { throw new Error(`Transaction not found: ${id}`); } if (trans.is_parent && !trans.subtransactions?.length) { throw new Error(`Parent transaction ${id} has no subtransactions`); }
306-306
: Consider making error calculation more explicit.The function correctly handles readonly arrays, but the error calculation could be more explicit about the expected balance.
const expectedBalance = num(trans.amount); const actualBalance = 0; // Initial split has no amounts return { ...trans, is_parent: true, error: expectedBalance === actualBalance ? null : SplitTransactionError(actualBalance, trans), // ... rest of the code };
334-334
: Consider adding type guard for temporary transactions.While the function handles readonly arrays correctly, consider adding a type guard to ensure only temporary transactions are processed.
function isTemporaryTransaction(t: TransactionEntity): boolean { return isTemporaryId(t.id); } export function realizeTempTransactions( transactions: readonly TransactionEntity[], ): TransactionEntity[] { if (!transactions.every(isTemporaryTransaction)) { throw new Error('All transactions must be temporary'); } // ... rest of the code }
355-356
: Consider making amount calculation more robust.While the function correctly handles readonly arrays, the amount calculation could be more robust by handling null/undefined amounts.
const totalAmount = remainingChildTransactions .map(t => num(t.amount)) .reduce((total, amount) => total + amount, 0); const updatedParentTransaction = { ...parentTransaction, ...(!deleteParentTransaction ? { amount: totalAmount } : {}), };packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)
Line range hint
1817-1827
: Consider adding type checking and default value for theisLoading
prop.The
isLoading
prop is correctly added but could benefit from better type safety and default value handling.+ import PropTypes from 'prop-types'; // ... or if using TypeScript: + interface TransactionTableInnerProps { + isLoading?: boolean; + // ... other props + } function TransactionTableInner({ - isLoading, + isLoading = false, tableNavigator, tableRef, // ... other props }) {
2052-2052
: Consider enhancing the loading state visual feedback.While the loading prop is correctly passed to the Table component, consider adding a loading indicator or skeleton UI to improve user experience during data fetching.
<Table - loading={isLoading} + loading={isLoading} + loadingComponent={ + <View style={{ padding: 20, alignItems: 'center' }}> + <LoadingSpinner size="medium" /> + <Text style={{ marginTop: 10 }}>Loading transactions...</Text> + </View> + } navigator={tableNavigator}packages/loot-core/src/client/query-hooks.ts (1)
Line range hint
27-34
: Prevent state updates on unmounted components inonError
callbackThe
onError
callback in theliveQuery
does not check theisUnmounted
flag before callingsetError
. This could lead to state updates on an unmounted component, causing memory leaks or React warnings.Apply this diff to add the
isUnmounted
check:let live: null | LiveQuery<Response> = liveQuery<Response>(query, { onData: data => { if (!isUnmounted) { setData(data); setIsLoading(false); } }, - onError: setError, + onError: error => { + if (!isUnmounted) { + setError(error); + } + }, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
upcoming-release-notes/3708.md
is excluded by!**/*.md
📒 Files selected for processing (17)
packages/desktop-client/src/components/accounts/Balance.jsx
(8 hunks)packages/desktop-client/src/components/accounts/Header.tsx
(13 hunks)packages/desktop-client/src/components/accounts/Reconcile.tsx
(1 hunks)packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx
(1 hunks)packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx
(7 hunks)packages/desktop-client/src/components/schedules/ScheduleLink.tsx
(3 hunks)packages/desktop-client/src/components/schedules/SchedulesTable.tsx
(1 hunks)packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
(2 hunks)packages/desktop-client/src/components/table.tsx
(4 hunks)packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
(3 hunks)packages/desktop-client/src/components/transactions/TransactionList.jsx
(2 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.jsx
(3 hunks)packages/desktop-client/src/hooks/usePreviewTransactions.ts
(1 hunks)packages/desktop-client/src/hooks/useSelected.tsx
(2 hunks)packages/loot-core/src/client/data-hooks/schedules.tsx
(1 hunks)packages/loot-core/src/client/query-hooks.ts
(1 hunks)packages/loot-core/src/shared/transactions.ts
(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
- packages/desktop-client/src/components/accounts/Header.tsx
- packages/desktop-client/src/components/accounts/Reconcile.tsx
- packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx
- packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx
- packages/desktop-client/src/components/schedules/ScheduleLink.tsx
- packages/desktop-client/src/components/schedules/SchedulesTable.tsx
- packages/desktop-client/src/components/spreadsheet/useSheetValue.ts
- packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/hooks/usePreviewTransactions.ts
- packages/desktop-client/src/hooks/useSelected.tsx
- packages/loot-core/src/client/data-hooks/schedules.tsx
🧰 Additional context used
📓 Learnings (1)
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-11-10T16:45:25.627Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🔇 Additional comments (11)
packages/desktop-client/src/components/accounts/Balance.jsx (3)
117-126
: LGTM! Good defensive programming.
The addition of the || 0
fallback ensures that the balance display won't break with null/undefined values.
158-165
: LGTM! Good use of memoization.
The balanceBinding
is properly memoized with the correct dependencies (accountId
and balanceQuery
), which helps prevent unnecessary recalculations.
90-93
: Verify the schedule balance calculation logic.
The condition for calculating schedule balance has been modified to use accountId
. Please ensure this change maintains the correct accounting direction (credit/debit) for scheduled transactions across different accounts.
✅ Verification successful
Schedule balance calculation correctly handles accountId changes.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for other schedule balance calculations in the codebase
ast-grep --pattern 'scheduleBalance $op getScheduledAmount($amount)'
Length of output: 70
Script:
#!/bin/bash
# Search for scheduleBalance being incremented or decremented with getScheduledAmount
ast-grep --pattern 'scheduleBalance ($op:+= | -:= ) getScheduledAmount($amount)'
Length of output: 82
Script:
#!/bin/bash
# Search for scheduleBalance being incremented or decremented with getScheduledAmount
rg 'scheduleBalance\s*[\+\-]=\s*getScheduledAmount\('
Length of output: 291
packages/loot-core/src/shared/transactions.ts (5)
93-96
: LGTM! Note the sorting dependency.
The readonly modifier is appropriate for this function. Just note that this function relies on transactions being sorted with parents before children, which is enforced at the database layer.
110-113
: LGTM! Readonly modifier correctly applied.
The function properly handles readonly transactions array while extracting split transactions.
123-125
: LGTM! Immutability properly maintained.
The function correctly preserves immutability by creating a new array while ungrouping transactions.
139-139
: LGTM! Immutable grouping implementation.
The function properly handles readonly array while creating a new grouped transaction object.
229-229
: LGTM! Proper immutability handling.
The function correctly maintains immutability while adding split transactions.
packages/desktop-client/src/components/table.tsx (2)
827-827
: LGTM: Loading state implementation looks good
The changes properly implement loading state handling:
- Default value for
isLoading
is set tofalse
ButtonWithLoading
is used correctly with theisLoading
prop
Also applies to: 842-856
819-819
:
Make isLoading
optional in SelectedItemsButtonProps
Since you've provided a default value of false
for isLoading
in the function signature, you should make it optional in the type definition to maintain backward compatibility.
Apply this diff:
- isLoading: boolean;
+ isLoading?: boolean;
Likely invalid or redundant comment.
packages/loot-core/src/client/query-hooks.ts (1)
Line range hint 25-38
: Use useRef
for isUnmounted
to prevent stale closure issues
Using a local variable isUnmounted
inside useEffect
can lead to stale closure issues. Consider using useRef
to ensure the flag remains consistent across renders.
6898f76
to
45095cf
Compare
Follow up to #3685 to simplify fetching transactions throughout the codebase.
This PR is migrating the desktop
Account
component/page to a functional component so that it can use the newuseTransactions
hook from #3685This is a major rewrite of the component so I would like to get 2-3 approvals for this before merging