diff --git a/packages/desktop-client/src/components/ManageRules.js b/packages/desktop-client/src/components/ManageRules.js index 98b433d4fec6526ac604e25c3325ecfa6a558ed7..11c9e3737adefca46491184d1b032ac4a218b10f 100644 --- a/packages/desktop-client/src/components/ManageRules.js +++ b/packages/desktop-client/src/components/ManageRules.js @@ -14,6 +14,7 @@ import * as undo from 'loot-core/src/platform/client/undo'; import { mapField, friendlyOp } from 'loot-core/src/shared/rules'; import { describeSchedule } from 'loot-core/src/shared/schedules'; +import useCategories from '../hooks/useCategories'; import useSelected, { SelectedProvider } from '../hooks/useSelected'; import { theme } from '../style'; @@ -88,12 +89,19 @@ function ManageRulesContent({ isModal, payeeId, setLoading }) { let dispatch = useDispatch(); let { data: schedules } = SchedulesQuery.useQuery(); - let filterData = useSelector(state => ({ + let { list: categories } = useCategories(); + let state = useSelector(state => ({ payees: state.queries.payees, - categories: state.queries.categories.list, accounts: state.queries.accounts, schedules, })); + let filterData = useMemo( + () => ({ + ...state, + categories, + }), + [state, categories], + ); let filteredRules = useMemo( () => diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 7299f17abe4bc1e3aa91da6ed8e2a7168304816e..2dc2423bd3c5547bb622ee7446ff860bba360d44 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -5,6 +5,7 @@ import { useLocation } from 'react-router-dom'; import { send } from 'loot-core/src/platform/client/fetch'; import { useActions } from '../hooks/useActions'; +import useCategories from '../hooks/useCategories'; import useSyncServerStatus from '../hooks/useSyncServerStatus'; import { type CommonModalProps } from '../types/modals'; @@ -34,8 +35,7 @@ export default function Modals() { const modalStack = useSelector(state => state.modals.modalStack); const isHidden = useSelector(state => state.modals.isHidden); const accounts = useSelector(state => state.queries.accounts); - const categoryGroups = useSelector(state => state.queries.categories.grouped); - const categories = useSelector(state => state.queries.categories.list); + const { grouped: categoryGroups, list: categories } = useCategories(); const budgetId = useSelector( state => state.prefs.local && state.prefs.local.id, ); diff --git a/packages/desktop-client/src/components/accounts/Account.js b/packages/desktop-client/src/components/accounts/Account.js index 796b97f158560245b85352ccfeafe74ad4f413ea..da79a1fc5a85cc8d5c4f8378092f5fcfd7f931c3 100644 --- a/packages/desktop-client/src/components/accounts/Account.js +++ b/packages/desktop-client/src/components/accounts/Account.js @@ -26,6 +26,7 @@ import { import { applyChanges, groupById } from 'loot-core/src/shared/util'; import { authorizeBank } from '../../gocardless'; +import useCategories from '../../hooks/useCategories'; import { SelectedProviderWithItems } from '../../hooks/useSelected'; import { styles, theme } from '../../style'; import Button from '../common/Button'; @@ -272,9 +273,6 @@ class AccountInternal extends PureComponent { // Important that any async work happens last so that the // listeners are set up synchronously - if (this.props.categoryGroups.length === 0) { - await this.props.getCategories(); - } await this.props.initiallyLoadPayees(); await this.fetchTransactions(); @@ -1350,12 +1348,12 @@ export default function Account() { let params = useParams(); let location = useLocation(); + let { grouped: categoryGroups } = useCategories(); let state = useSelector(state => ({ newTransactions: state.queries.newTransactions, matchedTransactions: state.queries.matchedTransactions, accounts: state.queries.accounts, failedAccounts: state.account.failedAccounts, - categoryGroups: state.queries.categories.grouped, dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy', hideFraction: state.prefs.local.hideFraction || false, expandSplits: state.prefs.local['expand-splits'], @@ -1408,6 +1406,7 @@ export default function Account() { > <AccountHack {...state} + categoryGroups={categoryGroups} {...actionCreators} modalShowing={state.modalShowing} accountId={params.id} diff --git a/packages/desktop-client/src/components/budget/MobileBudget.js b/packages/desktop-client/src/components/budget/MobileBudget.js index 0486a5b329dfcc14d93857a763f002b83f5b81ec..559d42f42395d220cd4676b0051a18021f4fc81f 100644 --- a/packages/desktop-client/src/components/budget/MobileBudget.js +++ b/packages/desktop-client/src/components/budget/MobileBudget.js @@ -11,6 +11,7 @@ import { import * as monthUtils from 'loot-core/src/shared/months'; import { useActions } from '../../hooks/useActions'; +import useCategories from '../../hooks/useCategories'; import { useSetThemeColor } from '../../hooks/useSetThemeColor'; import AnimatedLoading from '../../icons/AnimatedLoading'; import { theme } from '../../style'; @@ -293,8 +294,7 @@ class Budget extends Component { } export default function BudgetWrapper() { - let categoryGroups = useSelector(state => state.queries.categories.grouped); - let categories = useSelector(state => state.queries.categories.list); + let { list: categories, grouped: categoryGroups } = useCategories(); let budgetType = useSelector( state => state.prefs.local.budgetType || 'rollover', ); diff --git a/packages/desktop-client/src/components/budget/index.js b/packages/desktop-client/src/components/budget/index.js index d5a0497c93dadbde4663d81d7800411a81ea945c..cbaa70ed428578461f0a670d7167a36b512604b2 100644 --- a/packages/desktop-client/src/components/budget/index.js +++ b/packages/desktop-client/src/components/budget/index.js @@ -24,6 +24,7 @@ import { import * as monthUtils from 'loot-core/src/shared/months'; import { useActions } from '../../hooks/useActions'; +import useCategories from '../../hooks/useCategories'; import useFeatureFlag from '../../hooks/useFeatureFlag'; import { styles } from '../../style'; import View from '../common/View'; @@ -468,7 +469,7 @@ export default function BudgetWrapper(props) { state => state.prefs.local.budgetType || 'rollover', ); let maxMonths = useSelector(state => state.prefs.global.maxMonths); - let categoryGroups = useSelector(state => state.queries.categories.grouped); + let { grouped: categoryGroups } = useCategories(); let actions = useActions(); let spreadsheet = useSpreadsheet(); diff --git a/packages/desktop-client/src/components/modals/EditField.js b/packages/desktop-client/src/components/modals/EditField.js index f4632dafee73d35543296e274389c124b67f4550..eee4d0eb4ee6e524c60fb6f5d7f0e36d8d5114fb 100644 --- a/packages/desktop-client/src/components/modals/EditField.js +++ b/packages/desktop-client/src/components/modals/EditField.js @@ -7,6 +7,7 @@ import { currentDay, dayFromDate } from 'loot-core/src/shared/months'; import { amountToInteger } from 'loot-core/src/shared/util'; import { useActions } from '../../hooks/useActions'; +import useCategories from '../../hooks/useCategories'; import { useResponsive } from '../../ResponsiveProvider'; import { colors } from '../../style'; import AccountAutocomplete from '../autocomplete/AccountAutocomplete'; @@ -22,7 +23,7 @@ export default function EditField({ modalProps, name, onSubmit }) { let dateFormat = useSelector( state => state.prefs.local.dateFormat || 'MM/dd/yyyy', ); - let categoryGroups = useSelector(state => state.queries.categories.grouped); + let { grouped: categoryGroups } = useCategories(); let accounts = useSelector(state => state.queries.accounts); let payees = useSelector(state => state.queries.payees); diff --git a/packages/desktop-client/src/components/payees/ManagePayeesWithData.js b/packages/desktop-client/src/components/payees/ManagePayeesWithData.js index 01cced13d59d8c6530d0fdde05811058a2d0f6b4..a99364df50331c7edc0c97c57050375101aa53e5 100644 --- a/packages/desktop-client/src/components/payees/ManagePayeesWithData.js +++ b/packages/desktop-client/src/components/payees/ManagePayeesWithData.js @@ -5,13 +5,14 @@ import { send, listen } from 'loot-core/src/platform/client/fetch'; import { applyChanges } from 'loot-core/src/shared/util'; import { useActions } from '../../hooks/useActions'; +import useCategories from '../../hooks/useCategories'; import { ManagePayees } from '.'; export default function ManagePayeesWithData({ initialSelectedIds }) { let initialPayees = useSelector(state => state.queries.payees); let lastUndoState = useSelector(state => state.app.lastUndoState); - let categoryGroups = useSelector(state => state.queries.categories.grouped); + let { grouped: categoryGroups } = useCategories(); let { initiallyLoadPayees, getPayees, setLastUndoState, pushModal } = useActions(); diff --git a/packages/desktop-client/src/components/rules/Value.tsx b/packages/desktop-client/src/components/rules/Value.tsx index e7d24448caecf6d1aadf737e0e13e128d303b816..74474f1ec02c964ce45c2a307e8dd29ed44ac699 100644 --- a/packages/desktop-client/src/components/rules/Value.tsx +++ b/packages/desktop-client/src/components/rules/Value.tsx @@ -7,6 +7,7 @@ import { getMonthYearFormat } from 'loot-core/src/shared/months'; import { getRecurringDescription } from 'loot-core/src/shared/schedules'; import { integerToCurrency } from 'loot-core/src/shared/util'; +import useCategories from '../../hooks/useCategories'; import { theme } from '../../style'; import LinkButton from '../common/LinkButton'; import Text from '../common/Text'; @@ -33,31 +34,23 @@ export default function Value<T>({ // @ts-expect-error fix this later describe = x => x.name, }: ValueProps<T>) { - let { data, dateFormat } = useSelector(state => { - let data; - if (dataProp) { - data = dataProp; - } else { - switch (field) { - case 'payee': - data = state.queries.payees; - break; - case 'category': - data = state.queries.categories.list; - break; - case 'account': - data = state.queries.accounts; - break; - default: - data = []; - } - } + let dateFormat = useSelector( + state => state.prefs.local.dateFormat || 'MM/dd/yyyy', + ); + let payees = useSelector(state => state.queries.payees); + let { list: categories } = useCategories(); + let accounts = useSelector(state => state.queries.accounts); + + let data = + dataProp || + (field === 'payee' + ? payees + : field === 'category' + ? categories + : field === 'account' + ? accounts + : []); - return { - data, - dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy', - }; - }); let [expanded, setExpanded] = useState(false); function onExpand(e) { @@ -98,7 +91,7 @@ export default function Value<T>({ if (valueIsRaw) { return value; } - if (data && data.length) { + if (data && Array.isArray(data)) { let item = data.find(item => item.id === value); if (item) { return describe(item); diff --git a/packages/desktop-client/src/components/transactions/MobileTransaction.js b/packages/desktop-client/src/components/transactions/MobileTransaction.js index 26a9e6a78dde9826ab8223551a9df720d8b44070..97caeeec26b108b3914b226cf8b4ac822525f225 100644 --- a/packages/desktop-client/src/components/transactions/MobileTransaction.js +++ b/packages/desktop-client/src/components/transactions/MobileTransaction.js @@ -6,7 +6,7 @@ import React, { useState, useRef, } from 'react'; -import { connect } from 'react-redux'; +import { useSelector } from 'react-redux'; import { useNavigate, useParams, Link } from 'react-router-dom'; import { useFocusRing } from '@react-aria/focus'; @@ -23,7 +23,6 @@ import { import { css } from 'glamor'; import memoizeOne from 'memoize-one'; -import * as actions from 'loot-core/src/client/actions'; import q, { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; @@ -43,6 +42,8 @@ import { groupById, } from 'loot-core/src/shared/util'; +import { useActions } from '../../hooks/useActions'; +import useCategories from '../../hooks/useCategories'; import { useSetThemeColor } from '../../hooks/useSetThemeColor'; import SvgAdd from '../../icons/v1/Add'; import CheveronLeft from '../../icons/v1/CheveronLeft'; @@ -847,16 +848,28 @@ function TransactionEditUnconnected(props) { ); } -export const TransactionEdit = connect( - state => ({ - categories: state.queries.categories.list, - payees: state.queries.payees, - lastTransaction: state.queries.lastTransaction, - accounts: state.queries.accounts, - dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy', - }), - actions, -)(TransactionEditUnconnected); +export const TransactionEdit = props => { + const { list: categories } = useCategories(); + const payees = useSelector(state => state.queries.payees); + const lastTransaction = useSelector(state => state.queries.lastTransaction); + const accounts = useSelector(state => state.queries.accounts); + const dateFormat = useSelector( + state => state.prefs.local.dateFormat || 'MM/dd/yyyy', + ); + const actions = useActions(); + + return ( + <TransactionEditUnconnected + {...props} + {...actions} + categories={categories} + payees={payees} + lastTransaction={lastTransaction} + accounts={accounts} + dateFormat={dateFormat} + /> + ); +}; class Transaction extends PureComponent { render() { diff --git a/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.js b/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.js index c171270bfa287c98ffa3e16d595b28f4c4e5ffbb..82165e15888bb67aa0294faab64ba3bad0746d9f 100644 --- a/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.js +++ b/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.js @@ -13,6 +13,7 @@ import { } from 'loot-core/src/client/reducers/queries'; import { integerToCurrency } from 'loot-core/src/shared/util'; +import useCategories from '../../hooks/useCategories'; import { useSelectedItems, useSelectedDispatch } from '../../hooks/useSelected'; import ArrowsSynchronize from '../../icons/v2/ArrowsSynchronize'; import { theme, styles } from '../../style'; @@ -141,11 +142,11 @@ export default function SimpleTransactionsTable({ fields = ['date', 'payee', 'amount'], style, }) { - let { payees, categories, accounts, dateFormat } = useSelector(state => { + let { grouped: categories } = useCategories(); + let { payees, accounts, dateFormat } = useSelector(state => { return { payees: state.queries.payees, accounts: state.queries.accounts, - categories: state.queries.categories.grouped, dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy', }; }); diff --git a/packages/desktop-client/src/components/util/GenericInput.js b/packages/desktop-client/src/components/util/GenericInput.js index 15e0120c7819ec4369919411fc43d13bef908e02..bbc7b8d3dce58eaf5f3a1a77fb65a0e528193633 100644 --- a/packages/desktop-client/src/components/util/GenericInput.js +++ b/packages/desktop-client/src/components/util/GenericInput.js @@ -3,6 +3,7 @@ import { useSelector } from 'react-redux'; import { getMonthYearFormat } from 'loot-core/src/shared/months'; +import useCategories from '../../hooks/useCategories'; import AccountAutocomplete from '../autocomplete/AccountAutocomplete'; import Autocomplete from '../autocomplete/Autocomplete'; import CategoryAutocomplete from '../autocomplete/CategoryAutocomplete'; @@ -24,13 +25,11 @@ export default function GenericInput({ style, onChange, }) { - let { saved, categoryGroups, dateFormat } = useSelector(state => { - return { - saved: state.queries.saved, - categoryGroups: state.queries.categories.grouped, - dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy', - }; - }); + let { grouped: categoryGroups } = useCategories(); + let saved = useSelector(state => state.queries.saved); + let dateFormat = useSelector( + state => state.prefs.local.dateFormat || 'MM/dd/yyyy', + ); // This makes the UI more resilient in case of faulty data if (multi && !Array.isArray(value)) { diff --git a/upcoming-release-notes/1597.md b/upcoming-release-notes/1597.md new file mode 100644 index 0000000000000000000000000000000000000000..ca53dc34b005b58eb4321a2e3589d687ec757ce9 --- /dev/null +++ b/upcoming-release-notes/1597.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Use `useCategories` hook everywhere categories are accessed.