Hi, I'll answer your question first and then paste the code:
2) The data come from the parent of the parent component. It changes the proips.data and the props.dataTimeStamp. This is comparing the data is too expensive and everytime we change the data we change the dataTimeStamp. We do it by calling a remote API and change the state of the parent's parent component. The usefull part is:
fetchData(this.props.selectedActivityGroups)
.then(response => {
if (response && responseApiCode.OK === response.code) {
const activities = this.setActivities(response.data.activities)
const enrolments = this.setEnrolmentAllocations(response.data.allocationsList)
const columns = [...extraColumns, ...response.data.columns]
this.setState({
activities: activities,
data: enrolments,
dataTimeStamp: new Date().getTime(),
columns: columns,
isSectioned: response.data.sectioned,
isGroupLocked: response.data.groupLocked,
permissions: response.data.permissions,
totalAllocated: enrolments.filter(stud => stud.status.allocated).length,
totalNotAllocated: enrolments.filter(stud => !stud.status.allocated).length,
isLoadingEnrolments: false,
})
}
})
.catch(error => {
console.log('Failed to fetch data', error)
})
3) The parent component is only a middle man, it does not update it's state buty it has a
shouldComponentUpdate method to stop re renders. The parent's parent is the one the update iot's state multiple time in different scenarios. The one that actually passes data to the component we care is the
fetchData
defined before.
1)
TABLE_COMPONENT
import {getValue} from '@syncfusion/ej2-base'
import {DataUtil} from '@syncfusion/ej2-data'
import {
ColumnChooser,
ColumnDirective,
ColumnMenu,
ColumnsDirective,
Edit,
ExcelExport,
Filter,
GridComponent,
Group,
Inject,
Page,
PdfExport,
Resize,
Selection,
Sort,
} from '@syncfusion/ej2-react-grids'
import {isEmpty, isEqual} from 'lodash'
import React from 'react'
import { memo } from 'react';
import './syncFusionStyle.css'
// Overriding DataUtil equal method
DataUtil.fnOperators.equal = function (actual, expected, ignoreCase, ignoreAccent) {
if (typeof actual === 'object' && actual !== null && !isEmpty(actual.displayValues)) {
return actual.displayValues.includes(expected)
} else {
if (ignoreAccent) {
actual = DataUtil.ignoreDiacritics(actual)
expected = DataUtil.ignoreDiacritics(expected)
}
if (ignoreCase) {
return DataUtil.toLowerCase(actual) === DataUtil.toLowerCase(expected)
}
return actual === expected
}
}
DataUtil.fnOperators.notequal = function (actual, expected, ignoreCase, ignoreAccent) {
if (typeof actual === 'object' && actual !== null && !isEmpty(actual.displayValues)) {
return !actual.displayValues.includes(expected)
} else {
if (ignoreAccent) {
actual = DataUtil.ignoreDiacritics(actual)
expected = DataUtil.ignoreDiacritics(expected)
}
return !DataUtil.fnOperators.equal(actual, expected, ignoreCase)
}
}
const ALLOWED_ACTION = {
filterchoicerequest: true,
filtersearchbegin: true,
filterbeforeopen: true,
}
class Table extends React.Component {
shouldComponentUpdate(nextProps, nextState, nextContext) {
const result =
!isEqual(this.props.dataTimeStamp, nextProps.dataTimeStamp) ||
!isEqual(this.props.isSelectionEmpty, nextProps.isSelectionEmpty)
return result
}
componentDidUpdate(prevProps, prevState) {
if (
this.props.isSelectionEmpty &&
!isEmpty(this.grid.getSelectedRows())
) {
console.log('this.grid.clearRowSelection')
this.grid.clearRowSelection()
this.grid.clearSelection()
let newStudentRecord=[]
this.props.onRowSelect(newStudentRecord)
}
}
customizeCell = args => {
if (args.column.field === this.props.customColumn && args.data && args.cell && this.props.renderCustomCell) {
const value = getValue(this.props.customColumn, args.data)
this.props.renderCustomCell(value, args.cell)
}
}
selectionModified = args => {
if (this.grid && args.target !== null) {
let selectedrecords = this.grid.getSelectedRecords().concat()
this.props.onRowSelect(selectedrecords)
}
}
actionBegin = args => {
if (args && ALLOWED_ACTION[args.requestType]) {
if (args.requestType === 'filterchoicerequest' || args.requestType === 'filtersearchbegin') {
args.filterChoiceCount = 5000
} else if (
args.requestType === 'filterbeforeopen' &&
args.columnName === this.props.customColumn &&
!isEmpty(this.props.customColumnFilters)
) {
let values = []
this.props.customColumnFilters.forEach(val => {
let obj = {}
obj[args.columnName] = val
values.push(obj)
})
args.filterModel.options.dataSource = values
}
}
}
render() {
return (
<GridComponent
ref={g => (this.grid = g)}
dataSource={this.props.data}
allowPaging={this.props.allowPaging}
disablePageWiseAggregates={true}
allowGrouping={this.props.allowGrouping}
allowSorting={this.props.allowSorting}
showColumnMenu={this.props.showColumnMenu}
allowExcelExport={this.props.allowExcelExport}
allowPdfExport={this.props.allowPdfExport}
allowFiltering={this.props.allowFiltering}
groupSettings={{showGroupedColumn: this.props.showGroupedColumn}}
pageSettings={{pageSize: this.props.pageSize}}
enableHover={this.props.selectionEnabled}
queryCellInfo={this.customizeCell}
showColumnChooser={true}
filterSettings={{type: 'Excel'}}
selectionSettings={{
mode: 'Row',
persistSelection: true,
type: 'Multiple',
checkboxMode: 'ResetOnRowClick',
}}
rowSelected={this.selectionModified}
rowDeselected={this.selectionModified}
allowSelection={this.props.selectionEnabled}
allowResizing={true}
actionBegin={this.actionBegin}
>
<ColumnsDirective>
<ColumnDirective type="checkbox" width="50" />
{this.props.columns &&
this.props.columns.map(col => {
const isPrimarykey = this.props.isPrimaryKey && this.props.isPrimaryKey(col)
return (
<ColumnDirective
key={col.id}
isPrimaryKey={isPrimarykey}
field={col.id}
headerText={col.label}
width={col.width}
format={col.format}
textAlign={col.align}
type={col.type}
visible={col.visible || false}
showInColumnChooser={col.showInColumnChooser}
autoFit={col.autoFit}
/>
)
})}
</ColumnsDirective>
<Inject
services={[
Selection,
Sort,
ColumnMenu,
ColumnChooser,
Filter,
Page,
Group,
ExcelExport,
Edit,
PdfExport,
Resize,
]}
/>
</GridComponent>
)
}
}
export default memo(Table)
PARENT_COMPONENT
import * as React from 'react'
import ReactDOM from 'react-dom'
import {injectIntl} from 'react-intl'
import {withStyles} from '@mui/styles'
import Table from '../../../../widget/Table'
import withFormMessages from '../../../../core/WithFormMessages'
import AllocateStatusIcon from '../../../student/allocate/AllocateStatusIcon'
import {isEqual} from 'lodash'
const SINGLE_COLUMNS = ['status', 'activityAllocated', 'firstName', 'lastName', 'studentCode']
const MULTI_COLUMNS = [
'status',
'activityAllocated',
'firstName',
'lastName',
'studentCode',
'subjectCode',
'activityGroupCode',
]
class StaffAllocateDataGrid extends React.Component {
constructor(props) {
super(props)
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
const result =
!isEqual(this.props.dataTimeStamp, nextProps.dataTimeStamp) ||
!isEqual(this.props.selectionEnabled, nextProps.selectionEnabled) ||
!isEqual(this.props.isMultiSelect, nextProps.isMultiSelect) ||
!isEqual(this.props.isActivitySelected, nextProps.isActivitySelected) ||
!isEqual(this.props.selectedRows, nextProps.selectedRows)
return result
}
renderCustomCell = (value, context) => {
if (value) {
const icons = this.getStatusIcons(value)
ReactDOM.render(
<React.Fragment>
<div style={{display: 'flex'}}>{icons}</div>
</React.Fragment>,
context
)
}
}
getStatusIcons = status => {
let icons = []
if (status) {
if (status.allocatedNoChangeAllowed) {
icons.push(
<AllocateStatusIcon
icon="ALLOCATED_NO_CHANGES"
key="ALLOCATED_NO_CHANGES"
title="Allocated No Student Change Allowed"
appFuncs={this.props.appFuncs}
/>
)
}
if (status.allocated) {
icons.push(
<AllocateStatusIcon
icon="ALLOCATED"
key="ALLOCATED"
title="Allocated"
appFuncs={this.props.appFuncs}
/>
)
} else {
if (status.readyPlus) {
icons.push(
<AllocateStatusIcon
icon="READY_PLUS"
key="READY_PLUS"
title={status.comboMessage}
appFuncs={this.props.appFuncs}
/>
)
} else if (status.ready) {
icons.push(
<AllocateStatusIcon icon="READY" key="READY" title="Ready" appFuncs={this.props.appFuncs} />
)
}
if (status.constrained) {
icons.push(
<AllocateStatusIcon
icon="CONSTRAINED"
key="CONSTRAINED"
title={status.constraintMessage}
appFuncs={this.props.appFuncs}
/>
)
}
if (status.clashed) {
icons.push(
<AllocateStatusIcon
icon="CLASHED"
key="CLASHED"
title={status.clashMessage}
appFuncs={this.props.appFuncs}
/>
)
}
if (status.problem) {
icons.push(
<AllocateStatusIcon
icon="PROBLEM"
key="PROBLEM"
title={status.problemMessage}
appFuncs={this.props.appFuncs}
/>
)
}
if (status.comboProblem) {
icons.push(
<AllocateStatusIcon
icon="PROBLEM"
key="COMBO_PROBLEM"
title={status.comboMessage}
appFuncs={this.props.appFuncs}
/>
)
}
}
}
return icons
}
getColumns = () => {
if (this.props.columns) {
const usedColumns = this.props.isMultiSelect ? MULTI_COLUMNS : SINGLE_COLUMNS
return this.props.columns.map(col => {
return {
id: col.id,
label: this.props.isMultiSelect && col.id === 'activityAllocated' ? 'Activity Code' : col.label,
visible: usedColumns.includes(col.id) ? true : false,
type: col.id === 'status' ? 'boolean' : 'string',
autoFit: this.props.isMultiSelect && col.id !== 'firstName' ? true : false,
showInColumnChooser: col.id === 'uniqueId' ? false : true,
}
})
} else {
return []
}
}
isPrimaryKey = col => {
return col.id === 'uniqueId'
}
getCustomColumnFilters = () => {
if (this.props.isMultiSelect || !this.props.isActivitySelected) {
return ['Allocated','Not Allocated']
} else {
return ['Allocated','Clash','Constraint','Problem','Ready','Ready+']
}
}
render() {
return (
<Table
columns={this.getColumns()}
data={this.props.data}
dataTimeStamp={this.props.dataTimeStamp}
allowPaging={true}
allowGrouping={false}
allowSorting={true}
showColumnMenu={true}
allowExcelExport={true}
allowPdfExport={true}
allowFiltering={true}
isPrimaryKey={this.isPrimaryKey}
showGroupedColumn={true}
renderCustomCell={this.renderCustomCell}
customColumn={'status'}
pageSize={11}
onRowSelect={this.props.onStudentSelect}
selectionEnabled={this.props.selectionEnabled}
customColumnFilters={this.getCustomColumnFilters()}
isSelectionEmpty={this.props.isSelectionEmpty}
/>
)
}
}
const styles = theme => ({
icon: {
fontSize: 25,
paddingRight: 5,
},
})
export default injectIntl(withStyles(styles)(withFormMessages(StaffAllocateDataGrid)))