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.
This commit is contained in:
chamikaJ
2025-06-13 16:37:03 +05:30
parent 4426b5f3ef
commit 25639afe1a
3 changed files with 106 additions and 57 deletions

View File

@@ -1,4 +1,5 @@
import { Flex } from 'antd';
import { useMemo, useCallback, memo } from 'react';
import { useTranslation } from 'react-i18next';
import ProjectStatusFilterDropdown from './project-status-filter-dropdown';
import ProjectHealthFilterDropdown from './project-health-filter-dropdown';
@@ -15,26 +16,39 @@ const ProjectsReportsFilters = () => {
const { t } = useTranslation('reporting-projects-filters');
const { searchQuery } = useAppSelector(state => state.projectReportsReducer);
return (
<Flex gap={8} align="center" justify="space-between">
// Memoize the search query handler to prevent recreation on every render
const handleSearchQueryChange = useCallback((text: string) => {
dispatch(setSearchQuery(text));
}, [dispatch]);
// Memoize the filter dropdowns container to prevent recreation on every render
const filterDropdowns = useMemo(() => (
<Flex gap={8} wrap={'wrap'}>
<ProjectStatusFilterDropdown />
<ProjectHealthFilterDropdown />
<ProjectCategoriesFilterDropdown />
<ProjectManagersFilterDropdown />
</Flex>
), []);
// Memoize the right side controls to prevent recreation on every render
const rightControls = useMemo(() => (
<Flex gap={12}>
<ProjectTableShowFieldsDropdown />
<CustomSearchbar
placeholderText={t('searchByNamePlaceholder')}
searchQuery={searchQuery}
setSearchQuery={text => dispatch(setSearchQuery(text))}
setSearchQuery={handleSearchQueryChange}
/>
</Flex>
), [t, searchQuery, handleSearchQueryChange]);
return (
<Flex gap={8} align="center" justify="space-between">
{filterDropdowns}
{rightControls}
</Flex>
);
};
export default ProjectsReportsFilters;
export default memo(ProjectsReportsFilters);

View File

@@ -1,4 +1,4 @@
import { useEffect, useState, useMemo } from 'react';
import { useEffect, useState, useMemo, useCallback, memo } from 'react';
import { Button, ConfigProvider, Flex, PaginationProps, Table, TableColumnsType } from 'antd';
import { useTranslation } from 'react-i18next';
import { createPortal } from 'react-dom';
@@ -63,10 +63,11 @@ const ProjectsReportsTable = () => {
const columnsVisibility = useAppSelector(state => state.projectReportsTableColumnsReducer);
const handleDrawerOpen = (record: IRPTProject) => {
// Memoize the drawer open handler to prevent recreation on every render
const handleDrawerOpen = useCallback((record: IRPTProject) => {
setSelectedProject(record);
dispatch(toggleProjectReportsDrawer());
};
}, [dispatch]);
const columns: TableColumnsType<IRPTProject> = useMemo(
() => [
@@ -231,7 +232,7 @@ const ProjectsReportsTable = () => {
width: 200,
},
],
[t, order]
[t, order, handleDrawerOpen]
);
// filter columns based on the `hidden` state from Redux
@@ -240,12 +241,13 @@ const ProjectsReportsTable = () => {
[columns, columnsVisibility]
);
const handleTableChange = (pagination: PaginationProps, filters: any, sorter: any) => {
// Memoize the table change handler to prevent recreation on every render
const handleTableChange = useCallback((pagination: PaginationProps, filters: any, sorter: any) => {
if (sorter.order) dispatch(setOrder(sorter.order));
if (sorter.field) dispatch(setField(sorter.field));
dispatch(setIndex(pagination.current));
dispatch(setPageSize(pagination.pageSize));
};
}, [dispatch]);
useEffect(() => {
if (!isLoading) dispatch(fetchProjectData());
@@ -268,7 +270,7 @@ const ProjectsReportsTable = () => {
return () => {
dispatch(resetProjectReports());
};
}, []);
}, [dispatch]);
const tableRowProps = useMemo(
() => ({
@@ -292,27 +294,39 @@ const ProjectsReportsTable = () => {
[]
);
return (
<ConfigProvider {...tableConfig}>
<Table
columns={visibleColumns}
dataSource={projectList}
pagination={{
// Memoize pagination configuration to prevent recreation on every render
const paginationConfig = useMemo(() => ({
showSizeChanger: true,
defaultPageSize: 10,
total: total,
current: index,
pageSizeOptions: PAGE_SIZE_OPTIONS,
}}
scroll={{ x: 'max-content' }}
}), [total, index]);
// Memoize scroll configuration to prevent recreation on every render
const scrollConfig = useMemo(() => ({ x: 'max-content' }), []);
// Memoize row key function to prevent recreation on every render
const getRowKey = useCallback((record: IRPTProject) => record.id, []);
// Memoize onRow function to prevent recreation on every render
const getRowProps = useCallback(() => tableRowProps, [tableRowProps]);
return (
<ConfigProvider {...tableConfig}>
<Table
columns={visibleColumns}
dataSource={projectList}
pagination={paginationConfig}
scroll={scrollConfig}
loading={isLoading}
onChange={handleTableChange}
rowKey={record => record.id}
onRow={() => tableRowProps}
rowKey={getRowKey}
onRow={getRowProps}
/>
{createPortal(<ProjectReportsDrawer selectedProject={selectedProject} />, document.body)}
</ConfigProvider>
);
};
export default ProjectsReportsTable;
export default memo(ProjectsReportsTable);

View File

@@ -1,4 +1,5 @@
import { Button, Card, Checkbox, Dropdown, Flex, Space, Typography } from 'antd';
import { useMemo, useCallback, memo } from 'react';
import CustomPageHeader from '@/pages/reporting/page-header/custom-page-header';
import { DownOutlined } from '@ant-design/icons';
import ProjectReportsTable from './projects-reports-table/projects-reports-table';
@@ -20,40 +21,60 @@ const ProjectsReports = () => {
const { total, archived } = useAppSelector(state => state.projectReportsReducer);
const handleExcelExport = () => {
// Memoize the title to prevent recalculation on every render
const pageTitle = useMemo(() => {
return `${total === 1 ? `${total} ${t('projectCount')}` : `${total} ${t('projectCountPlural')}`} `;
}, [total, t]);
// Memoize the Excel export handler to prevent recreation on every render
const handleExcelExport = useCallback(() => {
if (currentSession?.team_name) {
reportingExportApiService.exportProjects(currentSession.team_name);
}
};
}, [currentSession?.team_name]);
return (
<Flex vertical>
<CustomPageHeader
title={`${total === 1 ? `${total} ${t('projectCount')}` : `${total} ${t('projectCountPlural')}`} `}
children={
// Memoize the archived checkbox handler to prevent recreation on every render
const handleArchivedChange = useCallback(() => {
dispatch(setArchived(!archived));
}, [dispatch, archived]);
// Memoize the dropdown menu items to prevent recreation on every render
const dropdownMenuItems = useMemo(() => [
{ key: '1', label: t('excelButton'), onClick: handleExcelExport }
], [t, handleExcelExport]);
// Memoize the header children to prevent recreation on every render
const headerChildren = useMemo(() => (
<Space>
<Button>
<Checkbox checked={archived} onChange={() => dispatch(setArchived(!archived))}>
<Checkbox checked={archived} onChange={handleArchivedChange}>
<Typography.Text>{t('includeArchivedButton')}</Typography.Text>
</Checkbox>
</Button>
<Dropdown
menu={{ items: [{ key: '1', label: t('excelButton'), onClick: handleExcelExport }] }}
>
<Dropdown menu={{ items: dropdownMenuItems }}>
<Button type="primary" icon={<DownOutlined />} iconPosition="end">
{t('exportButton')}
</Button>
</Dropdown>
</Space>
}
), [archived, handleArchivedChange, t, dropdownMenuItems]);
// Memoize the card title to prevent recreation on every render
const cardTitle = useMemo(() => <ProjectsReportsFilters />, []);
return (
<Flex vertical>
<CustomPageHeader
title={pageTitle}
children={headerChildren}
/>
<Card title={<ProjectsReportsFilters />}>
<Card title={cardTitle}>
<ProjectReportsTable />
</Card>
</Flex>
);
};
export default ProjectsReports;
export default memo(ProjectsReports);