Skip to content
Snippets Groups Projects
Unverified Commit bdbf6e9c authored by Joel Jeremy Marquez's avatar Joel Jeremy Marquez Committed by GitHub
Browse files

[Maintenance] Reduce budget table re-renders (#3448)

* Reduce budget table re-renders

* Release notes
parent c5193b6d
No related branches found
No related tags found
No related merge requests found
// @ts-strict-ignore
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import {
......@@ -20,10 +20,6 @@ import {
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send, listen } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import {
type CategoryEntity,
type CategoryGroupEntity,
} from 'loot-core/src/types/models';
import { useCategories } from '../../../hooks/useCategories';
import { useLocalPref } from '../../../hooks/useLocalPref';
......@@ -43,15 +39,13 @@ function isBudgetType(input?: string): input is 'rollover' | 'report' {
return ['rollover', 'report'].includes(input);
}
type BudgetInnerProps = {
categories: CategoryEntity[];
categoryGroups: CategoryGroupEntity[];
budgetType: 'rollover' | 'report';
spreadsheet: ReturnType<typeof useSpreadsheet>;
};
export function Budget() {
useSetThemeColor(theme.mobileViewTheme);
function BudgetInner(props: BudgetInnerProps) {
const { categoryGroups, categories, budgetType, spreadsheet } = props;
const { list: categories, grouped: categoryGroups } = useCategories();
const [budgetTypePref] = useMetadataPref('budgetType');
const budgetType = isBudgetType(budgetTypePref) ? budgetTypePref : 'rollover';
const spreadsheet = useSpreadsheet();
const currMonth = monthUtils.currentMonth();
const [startMonth = currMonth, setStartMonthPref] =
......@@ -60,9 +54,8 @@ function BudgetInner(props: BudgetInnerProps) {
start: startMonth,
end: startMonth,
});
const [initialized, setInitialized] = useState(false);
// const [editMode, setEditMode] = useState(false);
const [initialized, setInitialized] = useState(false);
const [_numberFormat] = useSyncedPref('numberFormat');
const numberFormat = _numberFormat || 'comma-dot';
const [hideFraction] = useSyncedPref('hideFraction');
......@@ -95,11 +88,14 @@ function BudgetInner(props: BudgetInnerProps) {
return () => unlisten();
}, [budgetType, startMonth, dispatch, spreadsheet]);
const onBudgetAction = async (month, type, args) => {
dispatch(applyBudgetAction(month, type, args));
};
const onBudgetAction = useCallback(
async (month, type, args) => {
dispatch(applyBudgetAction(month, type, args));
},
[dispatch],
);
const onShowBudgetSummary = () => {
const onShowBudgetSummary = useCallback(() => {
if (budgetType === 'report') {
dispatch(
pushModal('report-budget-summary', {
......@@ -114,9 +110,9 @@ function BudgetInner(props: BudgetInnerProps) {
}),
);
}
};
}, [budgetType, dispatch, onBudgetAction, startMonth]);
const onOpenNewCategoryGroupModal = () => {
const onOpenNewCategoryGroupModal = useCallback(() => {
dispatch(
pushModal('new-category-group', {
onValidate: name => (!name ? 'Name is required.' : null),
......@@ -126,152 +122,180 @@ function BudgetInner(props: BudgetInnerProps) {
},
}),
);
};
const onOpenNewCategoryModal = (groupId, isIncome) => {
dispatch(
pushModal('new-category', {
onValidate: name => (!name ? 'Name is required.' : null),
onSubmit: async name => {
dispatch(collapseModals('category-group-menu'));
dispatch(createCategory(name, groupId, isIncome, false));
},
}),
);
};
const onSaveGroup = group => {
dispatch(updateGroup(group));
};
const onDeleteGroup = async groupId => {
const group = categoryGroups?.find(g => g.id === groupId);
if (!group) {
return;
}
let mustTransfer = false;
for (const category of group.categories ?? []) {
if (await send('must-category-transfer', { id: category.id })) {
mustTransfer = true;
break;
}
}
}, [dispatch]);
if (mustTransfer) {
const onOpenNewCategoryModal = useCallback(
(groupId, isIncome) => {
dispatch(
pushModal('confirm-category-delete', {
group: groupId,
onDelete: transferCategory => {
pushModal('new-category', {
onValidate: name => (!name ? 'Name is required.' : null),
onSubmit: async name => {
dispatch(collapseModals('category-group-menu'));
dispatch(deleteGroup(groupId, transferCategory));
dispatch(createCategory(name, groupId, isIncome, false));
},
}),
);
} else {
dispatch(collapseModals('category-group-menu'));
dispatch(deleteGroup(groupId));
}
};
const onToggleGroupVisibility = groupId => {
const group = categoryGroups.find(g => g.id === groupId);
onSaveGroup({
...group,
hidden: !!!group.hidden,
});
dispatch(collapseModals('category-group-menu'));
};
},
[dispatch],
);
const onSaveCategory = category => {
dispatch(updateCategory(category));
};
const onSaveGroup = useCallback(
group => {
dispatch(updateGroup(group));
},
[dispatch],
);
const onDeleteCategory = async categoryId => {
const mustTransfer = await send('must-category-transfer', {
id: categoryId,
});
const onDeleteGroup = useCallback(
async groupId => {
const group = categoryGroups?.find(g => g.id === groupId);
if (mustTransfer) {
dispatch(
pushModal('confirm-category-delete', {
category: categoryId,
onDelete: transferCategory => {
if (categoryId !== transferCategory) {
dispatch(collapseModals('category-menu'));
dispatch(deleteCategory(categoryId, transferCategory));
}
},
}),
);
} else {
dispatch(collapseModals('category-menu'));
dispatch(deleteCategory(categoryId));
}
};
if (!group) {
return;
}
const onToggleCategoryVisibility = categoryId => {
const category = categories.find(c => c.id === categoryId);
onSaveCategory({
...category,
hidden: !!!category.hidden,
});
dispatch(collapseModals('category-menu'));
};
let mustTransfer = false;
for (const category of group.categories ?? []) {
if (await send('must-category-transfer', { id: category.id })) {
mustTransfer = true;
break;
}
}
const onReorderCategory = (id, { inGroup, aroundCategory }) => {
let groupId, targetId;
if (mustTransfer) {
dispatch(
pushModal('confirm-category-delete', {
group: groupId,
onDelete: transferCategory => {
dispatch(collapseModals('category-group-menu'));
dispatch(deleteGroup(groupId, transferCategory));
},
}),
);
} else {
dispatch(collapseModals('category-group-menu'));
dispatch(deleteGroup(groupId));
}
},
[categoryGroups, dispatch],
);
if (inGroup) {
groupId = inGroup;
} else if (aroundCategory) {
const { id: originalCatId, position } = aroundCategory;
const onToggleGroupVisibility = useCallback(
groupId => {
const group = categoryGroups.find(g => g.id === groupId);
onSaveGroup({
...group,
hidden: !!!group.hidden,
});
dispatch(collapseModals('category-group-menu'));
},
[categoryGroups, dispatch, onSaveGroup],
);
let catId = originalCatId;
const group = categoryGroups.find(group =>
group.categories?.find(cat => cat.id === catId),
);
const onSaveCategory = useCallback(
category => {
dispatch(updateCategory(category));
},
[dispatch],
);
if (position === 'bottom') {
const idx = group?.categories?.findIndex(cat => cat.id === catId) ?? -1;
catId = group?.categories
? idx < group.categories.length - 1
? group.categories[idx + 1].id
: null
: null;
const onDeleteCategory = useCallback(
async categoryId => {
const mustTransfer = await send('must-category-transfer', {
id: categoryId,
});
if (mustTransfer) {
dispatch(
pushModal('confirm-category-delete', {
category: categoryId,
onDelete: transferCategory => {
if (categoryId !== transferCategory) {
dispatch(collapseModals('category-menu'));
dispatch(deleteCategory(categoryId, transferCategory));
}
},
}),
);
} else {
dispatch(collapseModals('category-menu'));
dispatch(deleteCategory(categoryId));
}
},
[dispatch],
);
groupId = group?.id;
targetId = catId;
}
const onToggleCategoryVisibility = useCallback(
categoryId => {
const category = categories.find(c => c.id === categoryId);
onSaveCategory({
...category,
hidden: !!!category.hidden,
});
dispatch(collapseModals('category-menu'));
},
[categories, dispatch, onSaveCategory],
);
dispatch(moveCategory(id, groupId, targetId));
};
const onReorderCategory = useCallback(
(id, { inGroup, aroundCategory }) => {
let groupId, targetId;
if (inGroup) {
groupId = inGroup;
} else if (aroundCategory) {
const { id: originalCatId, position } = aroundCategory;
let catId = originalCatId;
const group = categoryGroups.find(group =>
group.categories?.find(cat => cat.id === catId),
);
if (position === 'bottom') {
const idx =
group?.categories?.findIndex(cat => cat.id === catId) ?? -1;
catId = group?.categories
? idx < group.categories.length - 1
? group.categories[idx + 1].id
: null
: null;
}
groupId = group?.id;
targetId = catId;
}
const onReorderGroup = (id, targetId, position) => {
if (position === 'bottom') {
const idx = categoryGroups.findIndex(group => group.id === targetId);
targetId =
idx < categoryGroups.length - 1 ? categoryGroups[idx + 1].id : null;
}
dispatch(moveCategory(id, groupId, targetId));
},
[categoryGroups, dispatch],
);
dispatch(moveCategoryGroup(id, targetId));
};
const onReorderGroup = useCallback(
(id, targetId, position) => {
if (position === 'bottom') {
const idx = categoryGroups.findIndex(group => group.id === targetId);
targetId =
idx < categoryGroups.length - 1 ? categoryGroups[idx + 1].id : null;
}
dispatch(moveCategoryGroup(id, targetId));
},
[categoryGroups, dispatch],
);
const onPrevMonth = async () => {
const onPrevMonth = useCallback(async () => {
const month = monthUtils.subMonths(startMonth, 1);
await prewarmMonth(budgetType, spreadsheet, month);
setStartMonthPref(month);
setInitialized(true);
};
}, [budgetType, setStartMonthPref, spreadsheet, startMonth]);
const onNextMonth = async () => {
const onNextMonth = useCallback(async () => {
const month = monthUtils.addMonths(startMonth, 1);
await prewarmMonth(budgetType, spreadsheet, month);
setStartMonthPref(month);
setInitialized(true);
};
}, [budgetType, setStartMonthPref, spreadsheet, startMonth]);
// const onOpenMonthActionMenu = () => {
// const options = [
......@@ -312,94 +336,128 @@ function BudgetInner(props: BudgetInnerProps) {
// );
// };
const onSaveNotes = async (id, notes) => {
const onSaveNotes = useCallback(async (id, notes) => {
await send('notes-save', { id, note: notes });
};
}, []);
const onOpenCategoryGroupNotesModal = id => {
const group = categoryGroups.find(g => g.id === id);
dispatch(
pushModal('notes', {
id,
name: group.name,
onSave: onSaveNotes,
}),
);
};
const onOpenCategoryGroupNotesModal = useCallback(
id => {
const group = categoryGroups.find(g => g.id === id);
dispatch(
pushModal('notes', {
id,
name: group.name,
onSave: onSaveNotes,
}),
);
},
[categoryGroups, dispatch, onSaveNotes],
);
const onOpenCategoryNotesModal = id => {
const category = categories.find(c => c.id === id);
dispatch(
pushModal('notes', {
id,
name: category.name,
onSave: onSaveNotes,
}),
);
};
const onOpenCategoryNotesModal = useCallback(
id => {
const category = categories.find(c => c.id === id);
dispatch(
pushModal('notes', {
id,
name: category.name,
onSave: onSaveNotes,
}),
);
},
[categories, dispatch, onSaveNotes],
);
const onOpenCategoryGroupMenuModal = id => {
const group = categoryGroups.find(g => g.id === id);
dispatch(
pushModal('category-group-menu', {
groupId: group.id,
onSave: onSaveGroup,
onAddCategory: onOpenNewCategoryModal,
onEditNotes: onOpenCategoryGroupNotesModal,
onDelete: onDeleteGroup,
onToggleVisibility: onToggleGroupVisibility,
}),
);
};
const onOpenCategoryGroupMenuModal = useCallback(
id => {
const group = categoryGroups.find(g => g.id === id);
dispatch(
pushModal('category-group-menu', {
groupId: group.id,
onSave: onSaveGroup,
onAddCategory: onOpenNewCategoryModal,
onEditNotes: onOpenCategoryGroupNotesModal,
onDelete: onDeleteGroup,
onToggleVisibility: onToggleGroupVisibility,
}),
);
},
[
categoryGroups,
dispatch,
onDeleteGroup,
onOpenCategoryGroupNotesModal,
onOpenNewCategoryModal,
onSaveGroup,
onToggleGroupVisibility,
],
);
const onOpenCategoryMenuModal = id => {
const category = categories.find(c => c.id === id);
dispatch(
pushModal('category-menu', {
categoryId: category.id,
onSave: onSaveCategory,
onEditNotes: onOpenCategoryNotesModal,
onDelete: onDeleteCategory,
onToggleVisibility: onToggleCategoryVisibility,
onBudgetAction,
}),
);
};
const onOpenCategoryMenuModal = useCallback(
id => {
const category = categories.find(c => c.id === id);
dispatch(
pushModal('category-menu', {
categoryId: category.id,
onSave: onSaveCategory,
onEditNotes: onOpenCategoryNotesModal,
onDelete: onDeleteCategory,
onToggleVisibility: onToggleCategoryVisibility,
onBudgetAction,
}),
);
},
[
categories,
dispatch,
onBudgetAction,
onDeleteCategory,
onOpenCategoryNotesModal,
onSaveCategory,
onToggleCategoryVisibility,
],
);
const [showHiddenCategories, setShowHiddenCategoriesPref] = useLocalPref(
'budget.showHiddenCategories',
);
const onToggleHiddenCategories = () => {
const onToggleHiddenCategories = useCallback(() => {
setShowHiddenCategoriesPref(!showHiddenCategories);
dispatch(collapseModals('budget-page-menu'));
};
}, [dispatch, setShowHiddenCategoriesPref, showHiddenCategories]);
const onOpenBudgetMonthNotesModal = month => {
dispatch(
pushModal('notes', {
id: `budget-${month}`,
name: monthUtils.format(month, 'MMMM ‘yy'),
onSave: onSaveNotes,
}),
);
};
const onOpenBudgetMonthNotesModal = useCallback(
month => {
dispatch(
pushModal('notes', {
id: `budget-${month}`,
name: monthUtils.format(month, 'MMMM ‘yy'),
onSave: onSaveNotes,
}),
);
},
[dispatch, onSaveNotes],
);
const onSwitchBudgetFile = () => {
const onSwitchBudgetFile = useCallback(() => {
dispatch(pushModal('budget-list'));
};
}, [dispatch]);
const onOpenBudgetMonthMenu = month => {
dispatch(
pushModal(`${budgetType}-budget-month-menu`, {
month,
onBudgetAction,
onEditNotes: onOpenBudgetMonthNotesModal,
}),
);
};
const onOpenBudgetMonthMenu = useCallback(
month => {
dispatch(
pushModal(`${budgetType}-budget-month-menu`, {
month,
onBudgetAction,
onEditNotes: onOpenBudgetMonthNotesModal,
}),
);
},
[budgetType, dispatch, onBudgetAction, onOpenBudgetMonthNotesModal],
);
const onOpenBudgetPageMenu = () => {
const onOpenBudgetPageMenu = useCallback(() => {
dispatch(
pushModal('budget-page-menu', {
onAddCategoryGroup: onOpenNewCategoryGroupModal,
......@@ -407,7 +465,12 @@ function BudgetInner(props: BudgetInnerProps) {
onSwitchBudgetFile,
}),
);
};
}, [
dispatch,
onOpenNewCategoryGroupModal,
onSwitchBudgetFile,
onToggleHiddenCategories,
]);
if (!categoryGroups || !initialized) {
return (
......@@ -464,18 +527,3 @@ function BudgetInner(props: BudgetInnerProps) {
</NamespaceContext.Provider>
);
}
export function Budget() {
const { list: categories, grouped: categoryGroups } = useCategories();
const [budgetType] = useMetadataPref('budgetType');
const spreadsheet = useSpreadsheet();
useSetThemeColor(theme.mobileViewTheme);
return (
<BudgetInner
categoryGroups={categoryGroups}
categories={categories}
budgetType={isBudgetType(budgetType) ? budgetType : 'rollover'}
spreadsheet={spreadsheet}
/>
);
}
---
category: Maintenance
authors: [joel-jeremy]
---
Reduce budget table re-renders
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment