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 { Flex } from 'antd';
import { useMemo, useCallback, memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import ProjectStatusFilterDropdown from './project-status-filter-dropdown'; import ProjectStatusFilterDropdown from './project-status-filter-dropdown';
import ProjectHealthFilterDropdown from './project-health-filter-dropdown'; import ProjectHealthFilterDropdown from './project-health-filter-dropdown';
@@ -15,26 +16,39 @@ const ProjectsReportsFilters = () => {
const { t } = useTranslation('reporting-projects-filters'); const { t } = useTranslation('reporting-projects-filters');
const { searchQuery } = useAppSelector(state => state.projectReportsReducer); const { searchQuery } = useAppSelector(state => state.projectReportsReducer);
// 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={handleSearchQueryChange}
/>
</Flex>
), [t, searchQuery, handleSearchQueryChange]);
return ( return (
<Flex gap={8} align="center" justify="space-between"> <Flex gap={8} align="center" justify="space-between">
<Flex gap={8} wrap={'wrap'}> {filterDropdowns}
<ProjectStatusFilterDropdown /> {rightControls}
<ProjectHealthFilterDropdown />
<ProjectCategoriesFilterDropdown />
<ProjectManagersFilterDropdown />
</Flex>
<Flex gap={12}>
<ProjectTableShowFieldsDropdown />
<CustomSearchbar
placeholderText={t('searchByNamePlaceholder')}
searchQuery={searchQuery}
setSearchQuery={text => dispatch(setSearchQuery(text))}
/>
</Flex>
</Flex> </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 { Button, ConfigProvider, Flex, PaginationProps, Table, TableColumnsType } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
@@ -63,10 +63,11 @@ const ProjectsReportsTable = () => {
const columnsVisibility = useAppSelector(state => state.projectReportsTableColumnsReducer); 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); setSelectedProject(record);
dispatch(toggleProjectReportsDrawer()); dispatch(toggleProjectReportsDrawer());
}; }, [dispatch]);
const columns: TableColumnsType<IRPTProject> = useMemo( const columns: TableColumnsType<IRPTProject> = useMemo(
() => [ () => [
@@ -231,7 +232,7 @@ const ProjectsReportsTable = () => {
width: 200, width: 200,
}, },
], ],
[t, order] [t, order, handleDrawerOpen]
); );
// filter columns based on the `hidden` state from Redux // filter columns based on the `hidden` state from Redux
@@ -240,12 +241,13 @@ const ProjectsReportsTable = () => {
[columns, columnsVisibility] [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.order) dispatch(setOrder(sorter.order));
if (sorter.field) dispatch(setField(sorter.field)); if (sorter.field) dispatch(setField(sorter.field));
dispatch(setIndex(pagination.current)); dispatch(setIndex(pagination.current));
dispatch(setPageSize(pagination.pageSize)); dispatch(setPageSize(pagination.pageSize));
}; }, [dispatch]);
useEffect(() => { useEffect(() => {
if (!isLoading) dispatch(fetchProjectData()); if (!isLoading) dispatch(fetchProjectData());
@@ -268,7 +270,7 @@ const ProjectsReportsTable = () => {
return () => { return () => {
dispatch(resetProjectReports()); dispatch(resetProjectReports());
}; };
}, []); }, [dispatch]);
const tableRowProps = useMemo( const tableRowProps = useMemo(
() => ({ () => ({
@@ -292,27 +294,39 @@ const ProjectsReportsTable = () => {
[] []
); );
// Memoize pagination configuration to prevent recreation on every render
const paginationConfig = useMemo(() => ({
showSizeChanger: true,
defaultPageSize: 10,
total: total,
current: index,
pageSizeOptions: PAGE_SIZE_OPTIONS,
}), [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 ( return (
<ConfigProvider {...tableConfig}> <ConfigProvider {...tableConfig}>
<Table <Table
columns={visibleColumns} columns={visibleColumns}
dataSource={projectList} dataSource={projectList}
pagination={{ pagination={paginationConfig}
showSizeChanger: true, scroll={scrollConfig}
defaultPageSize: 10,
total: total,
current: index,
pageSizeOptions: PAGE_SIZE_OPTIONS,
}}
scroll={{ x: 'max-content' }}
loading={isLoading} loading={isLoading}
onChange={handleTableChange} onChange={handleTableChange}
rowKey={record => record.id} rowKey={getRowKey}
onRow={() => tableRowProps} onRow={getRowProps}
/> />
{createPortal(<ProjectReportsDrawer selectedProject={selectedProject} />, document.body)} {createPortal(<ProjectReportsDrawer selectedProject={selectedProject} />, document.body)}
</ConfigProvider> </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 { 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 CustomPageHeader from '@/pages/reporting/page-header/custom-page-header';
import { DownOutlined } from '@ant-design/icons'; import { DownOutlined } from '@ant-design/icons';
import ProjectReportsTable from './projects-reports-table/projects-reports-table'; import ProjectReportsTable from './projects-reports-table/projects-reports-table';
@@ -20,40 +21,60 @@ const ProjectsReports = () => {
const { total, archived } = useAppSelector(state => state.projectReportsReducer); 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) { if (currentSession?.team_name) {
reportingExportApiService.exportProjects(currentSession.team_name); reportingExportApiService.exportProjects(currentSession.team_name);
} }
}; }, [currentSession?.team_name]);
// 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={handleArchivedChange}>
<Typography.Text>{t('includeArchivedButton')}</Typography.Text>
</Checkbox>
</Button>
<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 ( return (
<Flex vertical> <Flex vertical>
<CustomPageHeader <CustomPageHeader
title={`${total === 1 ? `${total} ${t('projectCount')}` : `${total} ${t('projectCountPlural')}`} `} title={pageTitle}
children={ children={headerChildren}
<Space>
<Button>
<Checkbox checked={archived} onChange={() => dispatch(setArchived(!archived))}>
<Typography.Text>{t('includeArchivedButton')}</Typography.Text>
</Checkbox>
</Button>
<Dropdown
menu={{ items: [{ key: '1', label: t('excelButton'), onClick: handleExcelExport }] }}
>
<Button type="primary" icon={<DownOutlined />} iconPosition="end">
{t('exportButton')}
</Button>
</Dropdown>
</Space>
}
/> />
<Card title={<ProjectsReportsFilters />}> <Card title={cardTitle}>
<ProjectReportsTable /> <ProjectReportsTable />
</Card> </Card>
</Flex> </Flex>
); );
}; };
export default ProjectsReports; export default memo(ProjectsReports);