Skip to content

Commit 25639af

Browse files
committed
refactor(reporting): optimize project reports components with memoization
- Refactored ProjectsReports, ProjectsReportsFilters, and ProjectsReportsTable components to utilize React.memo, useCallback, and useMemo for improved performance and reduced unnecessary re-renders. - Memoized various handlers and configurations to enhance rendering efficiency and maintain responsiveness. - Updated component exports to use memoization, ensuring optimal performance during re-renders.
1 parent 4426b5f commit 25639af

3 files changed

Lines changed: 106 additions & 57 deletions

File tree

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Flex } from 'antd';
2+
import { useMemo, useCallback, memo } from 'react';
23
import { useTranslation } from 'react-i18next';
34
import ProjectStatusFilterDropdown from './project-status-filter-dropdown';
45
import ProjectHealthFilterDropdown from './project-health-filter-dropdown';
@@ -15,26 +16,39 @@ const ProjectsReportsFilters = () => {
1516
const { t } = useTranslation('reporting-projects-filters');
1617
const { searchQuery } = useAppSelector(state => state.projectReportsReducer);
1718

18-
return (
19-
<Flex gap={8} align="center" justify="space-between">
20-
<Flex gap={8} wrap={'wrap'}>
21-
<ProjectStatusFilterDropdown />
22-
<ProjectHealthFilterDropdown />
23-
<ProjectCategoriesFilterDropdown />
24-
<ProjectManagersFilterDropdown />
25-
</Flex>
19+
// Memoize the search query handler to prevent recreation on every render
20+
const handleSearchQueryChange = useCallback((text: string) => {
21+
dispatch(setSearchQuery(text));
22+
}, [dispatch]);
2623

27-
<Flex gap={12}>
28-
<ProjectTableShowFieldsDropdown />
24+
// Memoize the filter dropdowns container to prevent recreation on every render
25+
const filterDropdowns = useMemo(() => (
26+
<Flex gap={8} wrap={'wrap'}>
27+
<ProjectStatusFilterDropdown />
28+
<ProjectHealthFilterDropdown />
29+
<ProjectCategoriesFilterDropdown />
30+
<ProjectManagersFilterDropdown />
31+
</Flex>
32+
), []);
2933

30-
<CustomSearchbar
31-
placeholderText={t('searchByNamePlaceholder')}
32-
searchQuery={searchQuery}
33-
setSearchQuery={text => dispatch(setSearchQuery(text))}
34-
/>
35-
</Flex>
34+
// Memoize the right side controls to prevent recreation on every render
35+
const rightControls = useMemo(() => (
36+
<Flex gap={12}>
37+
<ProjectTableShowFieldsDropdown />
38+
<CustomSearchbar
39+
placeholderText={t('searchByNamePlaceholder')}
40+
searchQuery={searchQuery}
41+
setSearchQuery={handleSearchQueryChange}
42+
/>
43+
</Flex>
44+
), [t, searchQuery, handleSearchQueryChange]);
45+
46+
return (
47+
<Flex gap={8} align="center" justify="space-between">
48+
{filterDropdowns}
49+
{rightControls}
3650
</Flex>
3751
);
3852
};
3953

40-
export default ProjectsReportsFilters;
54+
export default memo(ProjectsReportsFilters);

worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/projects-reports-table.tsx

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState, useMemo } from 'react';
1+
import { useEffect, useState, useMemo, useCallback, memo } from 'react';
22
import { Button, ConfigProvider, Flex, PaginationProps, Table, TableColumnsType } from 'antd';
33
import { useTranslation } from 'react-i18next';
44
import { createPortal } from 'react-dom';
@@ -63,10 +63,11 @@ const ProjectsReportsTable = () => {
6363

6464
const columnsVisibility = useAppSelector(state => state.projectReportsTableColumnsReducer);
6565

66-
const handleDrawerOpen = (record: IRPTProject) => {
66+
// Memoize the drawer open handler to prevent recreation on every render
67+
const handleDrawerOpen = useCallback((record: IRPTProject) => {
6768
setSelectedProject(record);
6869
dispatch(toggleProjectReportsDrawer());
69-
};
70+
}, [dispatch]);
7071

7172
const columns: TableColumnsType<IRPTProject> = useMemo(
7273
() => [
@@ -231,7 +232,7 @@ const ProjectsReportsTable = () => {
231232
width: 200,
232233
},
233234
],
234-
[t, order]
235+
[t, order, handleDrawerOpen]
235236
);
236237

237238
// filter columns based on the `hidden` state from Redux
@@ -240,12 +241,13 @@ const ProjectsReportsTable = () => {
240241
[columns, columnsVisibility]
241242
);
242243

243-
const handleTableChange = (pagination: PaginationProps, filters: any, sorter: any) => {
244+
// Memoize the table change handler to prevent recreation on every render
245+
const handleTableChange = useCallback((pagination: PaginationProps, filters: any, sorter: any) => {
244246
if (sorter.order) dispatch(setOrder(sorter.order));
245247
if (sorter.field) dispatch(setField(sorter.field));
246248
dispatch(setIndex(pagination.current));
247249
dispatch(setPageSize(pagination.pageSize));
248-
};
250+
}, [dispatch]);
249251

250252
useEffect(() => {
251253
if (!isLoading) dispatch(fetchProjectData());
@@ -268,7 +270,7 @@ const ProjectsReportsTable = () => {
268270
return () => {
269271
dispatch(resetProjectReports());
270272
};
271-
}, []);
273+
}, [dispatch]);
272274

273275
const tableRowProps = useMemo(
274276
() => ({
@@ -292,27 +294,39 @@ const ProjectsReportsTable = () => {
292294
[]
293295
);
294296

297+
// Memoize pagination configuration to prevent recreation on every render
298+
const paginationConfig = useMemo(() => ({
299+
showSizeChanger: true,
300+
defaultPageSize: 10,
301+
total: total,
302+
current: index,
303+
pageSizeOptions: PAGE_SIZE_OPTIONS,
304+
}), [total, index]);
305+
306+
// Memoize scroll configuration to prevent recreation on every render
307+
const scrollConfig = useMemo(() => ({ x: 'max-content' }), []);
308+
309+
// Memoize row key function to prevent recreation on every render
310+
const getRowKey = useCallback((record: IRPTProject) => record.id, []);
311+
312+
// Memoize onRow function to prevent recreation on every render
313+
const getRowProps = useCallback(() => tableRowProps, [tableRowProps]);
314+
295315
return (
296316
<ConfigProvider {...tableConfig}>
297317
<Table
298318
columns={visibleColumns}
299319
dataSource={projectList}
300-
pagination={{
301-
showSizeChanger: true,
302-
defaultPageSize: 10,
303-
total: total,
304-
current: index,
305-
pageSizeOptions: PAGE_SIZE_OPTIONS,
306-
}}
307-
scroll={{ x: 'max-content' }}
320+
pagination={paginationConfig}
321+
scroll={scrollConfig}
308322
loading={isLoading}
309323
onChange={handleTableChange}
310-
rowKey={record => record.id}
311-
onRow={() => tableRowProps}
324+
rowKey={getRowKey}
325+
onRow={getRowProps}
312326
/>
313327
{createPortal(<ProjectReportsDrawer selectedProject={selectedProject} />, document.body)}
314328
</ConfigProvider>
315329
);
316330
};
317331

318-
export default ProjectsReportsTable;
332+
export default memo(ProjectsReportsTable);
Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Button, Card, Checkbox, Dropdown, Flex, Space, Typography } from 'antd';
2+
import { useMemo, useCallback, memo } from 'react';
23
import CustomPageHeader from '@/pages/reporting/page-header/custom-page-header';
34
import { DownOutlined } from '@ant-design/icons';
45
import ProjectReportsTable from './projects-reports-table/projects-reports-table';
@@ -20,40 +21,60 @@ const ProjectsReports = () => {
2021

2122
const { total, archived } = useAppSelector(state => state.projectReportsReducer);
2223

23-
const handleExcelExport = () => {
24+
// Memoize the title to prevent recalculation on every render
25+
const pageTitle = useMemo(() => {
26+
return `${total === 1 ? `${total} ${t('projectCount')}` : `${total} ${t('projectCountPlural')}`} `;
27+
}, [total, t]);
28+
29+
// Memoize the Excel export handler to prevent recreation on every render
30+
const handleExcelExport = useCallback(() => {
2431
if (currentSession?.team_name) {
2532
reportingExportApiService.exportProjects(currentSession.team_name);
2633
}
27-
};
34+
}, [currentSession?.team_name]);
35+
36+
// Memoize the archived checkbox handler to prevent recreation on every render
37+
const handleArchivedChange = useCallback(() => {
38+
dispatch(setArchived(!archived));
39+
}, [dispatch, archived]);
40+
41+
// Memoize the dropdown menu items to prevent recreation on every render
42+
const dropdownMenuItems = useMemo(() => [
43+
{ key: '1', label: t('excelButton'), onClick: handleExcelExport }
44+
], [t, handleExcelExport]);
45+
46+
// Memoize the header children to prevent recreation on every render
47+
const headerChildren = useMemo(() => (
48+
<Space>
49+
<Button>
50+
<Checkbox checked={archived} onChange={handleArchivedChange}>
51+
<Typography.Text>{t('includeArchivedButton')}</Typography.Text>
52+
</Checkbox>
53+
</Button>
54+
55+
<Dropdown menu={{ items: dropdownMenuItems }}>
56+
<Button type="primary" icon={<DownOutlined />} iconPosition="end">
57+
{t('exportButton')}
58+
</Button>
59+
</Dropdown>
60+
</Space>
61+
), [archived, handleArchivedChange, t, dropdownMenuItems]);
62+
63+
// Memoize the card title to prevent recreation on every render
64+
const cardTitle = useMemo(() => <ProjectsReportsFilters />, []);
2865

2966
return (
3067
<Flex vertical>
3168
<CustomPageHeader
32-
title={`${total === 1 ? `${total} ${t('projectCount')}` : `${total} ${t('projectCountPlural')}`} `}
33-
children={
34-
<Space>
35-
<Button>
36-
<Checkbox checked={archived} onChange={() => dispatch(setArchived(!archived))}>
37-
<Typography.Text>{t('includeArchivedButton')}</Typography.Text>
38-
</Checkbox>
39-
</Button>
40-
41-
<Dropdown
42-
menu={{ items: [{ key: '1', label: t('excelButton'), onClick: handleExcelExport }] }}
43-
>
44-
<Button type="primary" icon={<DownOutlined />} iconPosition="end">
45-
{t('exportButton')}
46-
</Button>
47-
</Dropdown>
48-
</Space>
49-
}
69+
title={pageTitle}
70+
children={headerChildren}
5071
/>
5172

52-
<Card title={<ProjectsReportsFilters />}>
73+
<Card title={cardTitle}>
5374
<ProjectReportsTable />
5475
</Card>
5576
</Flex>
5677
);
5778
};
5879

59-
export default ProjectsReports;
80+
export default memo(ProjectsReports);

0 commit comments

Comments
 (0)