Skip to content

Commit

Permalink
Hooks for frequently made operations (actualbudget#2293)
Browse files Browse the repository at this point in the history
* Hooks for frequently made operations

* Release notes

* Fix typecheck errors

* Remove useGlobalPrefs

* Add null checks

* Fix showCleared pref

* Add loaded flag for categories, accounts and payees state

* Refactor to reduce unnecessary states

* Fix eslint errors

* Fix hooks deps

* Add useEffect

* Fix typecheck error

* Set local and global pref hooks

* Fix lint error

* VRT

* Fix typecheck error

* Remove eager loading

* Fix typecheck error

* Fix typo

* Fix typecheck error

* Update useTheme

* Typecheck errors

* Typecheck error

* defaultValue

* Explicitly check undefined

* Remove useGlobalPref and useLocalPref defaults

* Fix default prefs

* Default value

* Fix lint error

* Set default theme

* Default date format in Account

* Update packages/desktop-client/src/style/theme.tsx

Co-authored-by: Matiss Janis Aboltins <[email protected]>

---------

Co-authored-by: Matiss Janis Aboltins <[email protected]>
  • Loading branch information
joel-jeremy and MatissJanis authored Feb 12, 2024
1 parent 4e9fbe9 commit 6a7bf34
Show file tree
Hide file tree
Showing 104 changed files with 1,045 additions and 1,492 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 9 additions & 38 deletions packages/desktop-client/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@ import {
} from 'react-error-boundary';
import { useSelector } from 'react-redux';

import { type State } from 'loot-core/client/state-types';
import { type AppState } from 'loot-core/client/state-types/app';
import { type PrefsState } from 'loot-core/client/state-types/prefs';
import * as Platform from 'loot-core/src/client/platform';
import { type State } from 'loot-core/src/client/state-types';
import {
init as initConnection,
send,
} from 'loot-core/src/platform/client/fetch';
import { type GlobalPrefs } from 'loot-core/src/types/prefs';

import { useActions } from '../hooks/useActions';
import { useLocalPref } from '../hooks/useLocalPref';
import { installPolyfills } from '../polyfills';
import { ResponsiveProvider } from '../ResponsiveProvider';
import { styles, hasHiddenScrollbars, ThemeStyle } from '../style';
Expand All @@ -34,26 +32,13 @@ import { UpdateNotification } from './UpdateNotification';
type AppInnerProps = {
budgetId: string;
cloudFileId: string;
loadingText: string;
loadBudget: (
id: string,
loadingText?: string,
options?: object,
) => Promise<void>;
closeBudget: () => Promise<void>;
loadGlobalPrefs: () => Promise<GlobalPrefs>;
};

function AppInner({
budgetId,
cloudFileId,
loadingText,
loadBudget,
closeBudget,
loadGlobalPrefs,
}: AppInnerProps) {
function AppInner({ budgetId, cloudFileId }: AppInnerProps) {
const [initializing, setInitializing] = useState(true);
const { showBoundary: showErrorBoundary } = useErrorBoundary();
const loadingText = useSelector((state: State) => state.app.loadingText);
const { loadBudget, closeBudget, loadGlobalPrefs } = useActions();

async function init() {
const socketName = await global.Actual.getServerSocket();
Expand Down Expand Up @@ -126,16 +111,9 @@ function ErrorFallback({ error }: FallbackProps) {
}

export function App() {
const budgetId = useSelector<State, PrefsState['local']['id']>(
state => state.prefs.local && state.prefs.local.id,
);
const cloudFileId = useSelector<State, PrefsState['local']['cloudFileId']>(
state => state.prefs.local && state.prefs.local.cloudFileId,
);
const loadingText = useSelector<State, AppState['loadingText']>(
state => state.app.loadingText,
);
const { loadBudget, closeBudget, loadGlobalPrefs, sync } = useActions();
const [budgetId] = useLocalPref('id');
const [cloudFileId] = useLocalPref('cloudFileId');
const { sync } = useActions();
const [hiddenScrollbars, setHiddenScrollbars] = useState(
hasHiddenScrollbars(),
);
Expand Down Expand Up @@ -184,14 +162,7 @@ export function App() {
{process.env.REACT_APP_REVIEW_ID && !Platform.isPlaywright && (
<DevelopmentTopBar />
)}
<AppInner
budgetId={budgetId}
cloudFileId={cloudFileId}
loadingText={loadingText}
loadBudget={loadBudget}
closeBudget={closeBudget}
loadGlobalPrefs={loadGlobalPrefs}
/>
<AppInner budgetId={budgetId} cloudFileId={cloudFileId} />
</ErrorBoundary>
<ThemeStyle />
</View>
Expand Down
7 changes: 3 additions & 4 deletions packages/desktop-client/src/components/BankSyncStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { useTransition, animated } from 'react-spring';

import { type State } from 'loot-core/client/state-types';
import { type AccountState } from 'loot-core/client/state-types/account';
import { type State } from 'loot-core/src/client/state-types';

import { theme, styles } from '../style';

Expand All @@ -12,8 +11,8 @@ import { Text } from './common/Text';
import { View } from './common/View';

export function BankSyncStatus() {
const accountsSyncing = useSelector<State, AccountState['accountsSyncing']>(
state => state.account.accountsSyncing,
const accountsSyncing = useSelector(
(state: State) => state.account.accountsSyncing,
);

const name = accountsSyncing
Expand Down
41 changes: 20 additions & 21 deletions packages/desktop-client/src/components/FinancesApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React, { type ReactElement, useEffect, useMemo } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend as Backend } from 'react-dnd-html5-backend';
import { useSelector } from 'react-redux';
import {
Route,
Routes,
Expand All @@ -13,12 +14,12 @@ import {

import hotkeys from 'hotkeys-js';

import { AccountsProvider } from 'loot-core/src/client/data-hooks/accounts';
import { PayeesProvider } from 'loot-core/src/client/data-hooks/payees';
import { SpreadsheetProvider } from 'loot-core/src/client/SpreadsheetProvider';
import { type State } from 'loot-core/src/client/state-types';
import { checkForUpdateNotification } from 'loot-core/src/client/update-notification';
import * as undo from 'loot-core/src/platform/client/undo';

import { useAccounts } from '../hooks/useAccounts';
import { useActions } from '../hooks/useActions';
import { useNavigate } from '../hooks/useNavigate';
import { useResponsive } from '../ResponsiveProvider';
Expand All @@ -39,7 +40,8 @@ import { Reports } from './reports';
import { NarrowAlternate, WideComponent } from './responsive';
import { ScrollProvider } from './ScrollProvider';
import { Settings } from './settings';
import { FloatableSidebar, SidebarProvider } from './sidebar';
import { FloatableSidebar } from './sidebar';
import { SidebarProvider } from './sidebar/SidebarProvider';
import { Titlebar, TitlebarProvider } from './Titlebar';
import { TransactionEdit } from './transactions/MobileTransaction';

Expand Down Expand Up @@ -71,18 +73,19 @@ function WideNotSupported({ children, redirectTo = '/budget' }) {
return isNarrowWidth ? children : null;
}

function RouterBehaviors({ getAccounts }) {
function RouterBehaviors() {
const navigate = useNavigate();
const accounts = useAccounts();
const accountsLoaded = useSelector(
(state: State) => state.queries.accountsLoaded,
);
useEffect(() => {
// Get the accounts and check if any exist. If there are no
// accounts, we want to redirect the user to the All Accounts
// screen which will prompt them to add an account
getAccounts().then(accounts => {
if (accounts.length === 0) {
navigate('/accounts');
}
});
}, []);
// If there are no accounts, we want to redirect the user to
// the All Accounts screen which will prompt them to add an account
if (accountsLoaded && accounts.length === 0) {
navigate('/accounts');
}
}, [accountsLoaded, accounts]);

const location = useLocation();
const href = useHref(location);
Expand Down Expand Up @@ -116,7 +119,7 @@ function FinancesAppWithoutContext() {

return (
<BrowserRouter>
<RouterBehaviors getAccounts={actions.getAccounts} />
<RouterBehaviors />
<ExposeNavigate />

<View style={{ height: '100%' }}>
Expand Down Expand Up @@ -265,13 +268,9 @@ export function FinancesApp() {
<TitlebarProvider>
<SidebarProvider>
<BudgetMonthCountProvider>
<PayeesProvider>
<AccountsProvider>
<DndProvider backend={Backend}>
<ScrollProvider>{app}</ScrollProvider>
</DndProvider>
</AccountsProvider>
</PayeesProvider>
<DndProvider backend={Backend}>
<ScrollProvider>{app}</ScrollProvider>
</DndProvider>
</BudgetMonthCountProvider>
</SidebarProvider>
</TitlebarProvider>
Expand Down
7 changes: 2 additions & 5 deletions packages/desktop-client/src/components/LoggedInUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';

import { type State } from 'loot-core/client/state-types';
import { type UserState } from 'loot-core/client/state-types/user';
import { type State } from 'loot-core/src/client/state-types';

import { useActions } from '../hooks/useActions';
import { theme, styles, type CSSProperties } from '../style';
Expand All @@ -25,9 +24,7 @@ export function LoggedInUser({
style,
color,
}: LoggedInUserProps) {
const userData = useSelector<State, UserState['data']>(
state => state.user.data,
);
const userData = useSelector((state: State) => state.user.data);
const { getUserData, signOut, closeBudget } = useActions();
const [loading, setLoading] = useState(true);
const [menuOpen, setMenuOpen] = useState(false);
Expand Down
23 changes: 9 additions & 14 deletions packages/desktop-client/src/components/ManageRules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import React, {
type SetStateAction,
type Dispatch,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';

import { type State } from 'loot-core/client/state-types';
import { type QueriesState } from 'loot-core/client/state-types/queries';
import { pushModal } from 'loot-core/src/client/actions/modals';
import { initiallyLoadPayees } from 'loot-core/src/client/actions/queries';
import { send } from 'loot-core/src/platform/client/fetch';
Expand All @@ -19,7 +17,9 @@ import { mapField, friendlyOp } from 'loot-core/src/shared/rules';
import { describeSchedule } from 'loot-core/src/shared/schedules';
import { type RuleEntity } from 'loot-core/src/types/models';

import { useAccounts } from '../hooks/useAccounts';
import { useCategories } from '../hooks/useCategories';
import { usePayees } from '../hooks/usePayees';
import { useSelected, SelectedProvider } from '../hooks/useSelected';
import { theme } from '../style';

Expand Down Expand Up @@ -105,18 +105,13 @@ function ManageRulesContent({

const { data: schedules } = SchedulesQuery.useQuery();
const { list: categories } = useCategories();
const state = useSelector<
State,
{
payees: QueriesState['payees'];
accounts: QueriesState['accounts'];
schedules: ReturnType<(typeof SchedulesQuery)['useQuery']>;
}
>(state => ({
payees: state.queries.payees,
accounts: state.queries.accounts,
const payees = usePayees();
const accounts = useAccounts();
const state = {
payees,
accounts,
schedules,
}));
};
const filterData = useMemo(
() => ({
...state,
Expand Down
20 changes: 5 additions & 15 deletions packages/desktop-client/src/components/MobileWebMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { savePrefs } from 'loot-core/src/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import { type PrefsState } from 'loot-core/src/client/state-types/prefs';

import { useLocalPref } from '../hooks/useLocalPref';
import { useResponsive } from '../ResponsiveProvider';
import { theme, styles } from '../style';

Expand All @@ -16,30 +12,24 @@ import { Checkbox } from './forms';
const buttonStyle = { border: 0, fontSize: 15, padding: '10px 13px' };

export function MobileWebMessage() {
const hideMobileMessagePref = useSelector<
State,
PrefsState['local']['hideMobileMessage']
>(state => {
return (state.prefs.local && state.prefs.local.hideMobileMessage) || true;
});
const [hideMobileMessage = true, setHideMobileMessagePref] =
useLocalPref('hideMobileMessage');

const { isNarrowWidth } = useResponsive();

const [show, setShow] = useState(
isNarrowWidth &&
!hideMobileMessagePref &&
!hideMobileMessage &&
!document.cookie.match(/hideMobileMessage=true/),
);
const [requestDontRemindMe, setRequestDontRemindMe] = useState(false);

const dispatch = useDispatch();

function onTry() {
setShow(false);

if (requestDontRemindMe) {
// remember the pref indefinitely
dispatch(savePrefs({ hideMobileMessage: true }));
setHideMobileMessagePref(true);
} else {
// Set a cookie for 5 minutes
const d = new Date();
Expand Down
39 changes: 6 additions & 33 deletions packages/desktop-client/src/components/Modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,10 @@ import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';

import { type State } from 'loot-core/src/client/state-types';
import {
type ModalsState,
type PopModalAction,
} from 'loot-core/src/client/state-types/modals';
import { type PrefsState } from 'loot-core/src/client/state-types/prefs';
import { type QueriesState } from 'loot-core/src/client/state-types/queries';
import { type PopModalAction } from 'loot-core/src/client/state-types/modals';
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 { CategoryGroupMenu } from './modals/CategoryGroupMenu';
Expand Down Expand Up @@ -56,19 +50,8 @@ export type CommonModalProps = {
};

export function Modals() {
const modalStack = useSelector<State, ModalsState['modalStack']>(
state => state.modals.modalStack,
);
const isHidden = useSelector<State, ModalsState['isHidden']>(
state => state.modals.isHidden,
);
const accounts = useSelector<State, QueriesState['accounts']>(
state => state.queries.accounts,
);
const { grouped: categoryGroups, list: categories } = useCategories();
const budgetId = useSelector<State, PrefsState['local']['id']>(
state => state.prefs.local && state.prefs.local.id,
);
const modalStack = useSelector((state: State) => state.modals.modalStack);
const isHidden = useSelector((state: State) => state.modals.isHidden);
const actions = useActions();
const location = useLocation();

Expand Down Expand Up @@ -118,8 +101,6 @@ export function Modals() {
account={options.account}
balance={options.balance}
canDelete={options.canDelete}
accounts={accounts.filter(acct => acct.closed === 0)}
categoryGroups={categoryGroups}
actions={actions}
/>
);
Expand All @@ -130,7 +111,6 @@ export function Modals() {
modalProps={modalProps}
externalAccounts={options.accounts}
requisitionId={options.requisitionId}
localAccounts={accounts.filter(acct => acct.closed === 0)}
actions={actions}
syncSource={options.syncSource}
/>
Expand All @@ -140,15 +120,8 @@ export function Modals() {
return (
<ConfirmCategoryDelete
modalProps={modalProps}
category={
'category' in options &&
categories.find(c => c.id === options.category)
}
group={
'group' in options &&
categoryGroups.find(g => g.id === options.group)
}
categoryGroups={categoryGroups}
category={options.category}
group={options.group}
onDelete={options.onDelete}
/>
);
Expand All @@ -166,7 +139,7 @@ export function Modals() {
return (
<LoadBackup
watchUpdates
budgetId={budgetId}
budgetId={options.budgetId}
modalProps={modalProps}
actions={actions}
backupDisabled={false}
Expand Down
Loading

0 comments on commit 6a7bf34

Please sign in to comment.