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:
@@ -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);
|
||||
|
||||
// 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 (
|
||||
<Flex gap={8} align="center" justify="space-between">
|
||||
<Flex gap={8} wrap={'wrap'}>
|
||||
<ProjectStatusFilterDropdown />
|
||||
<ProjectHealthFilterDropdown />
|
||||
<ProjectCategoriesFilterDropdown />
|
||||
<ProjectManagersFilterDropdown />
|
||||
</Flex>
|
||||
|
||||
<Flex gap={12}>
|
||||
<ProjectTableShowFieldsDropdown />
|
||||
|
||||
<CustomSearchbar
|
||||
placeholderText={t('searchByNamePlaceholder')}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={text => dispatch(setSearchQuery(text))}
|
||||
/>
|
||||
</Flex>
|
||||
{filterDropdowns}
|
||||
{rightControls}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectsReportsFilters;
|
||||
export default memo(ProjectsReportsFilters);
|
||||
|
||||
@@ -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 = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
// 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 (
|
||||
<ConfigProvider {...tableConfig}>
|
||||
<Table
|
||||
columns={visibleColumns}
|
||||
dataSource={projectList}
|
||||
pagination={{
|
||||
showSizeChanger: true,
|
||||
defaultPageSize: 10,
|
||||
total: total,
|
||||
current: index,
|
||||
pageSizeOptions: PAGE_SIZE_OPTIONS,
|
||||
}}
|
||||
scroll={{ x: 'max-content' }}
|
||||
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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
// 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 (
|
||||
<Flex vertical>
|
||||
<CustomPageHeader
|
||||
title={`${total === 1 ? `${total} ${t('projectCount')}` : `${total} ${t('projectCountPlural')}`} `}
|
||||
children={
|
||||
<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>
|
||||
}
|
||||
title={pageTitle}
|
||||
children={headerChildren}
|
||||
/>
|
||||
|
||||
<Card title={<ProjectsReportsFilters />}>
|
||||
<Card title={cardTitle}>
|
||||
<ProjectReportsTable />
|
||||
</Card>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectsReports;
|
||||
export default memo(ProjectsReports);
|
||||
|
||||
Reference in New Issue
Block a user