We use cookies to give you the best experience on our website. If you continue to browse, then you agree to our privacy policy and cookie policy. Image for the cookie policy date

Grid Performance is hindered by multiple refresh

Hello,

I'm using the following versions:

"@syncfusion/ej2-react-buttons": "20.4.48",
"@syncfusion/ej2-react-calendars": "20.4.48",
"@syncfusion/ej2-react-charts": "20.4.48",
"@syncfusion/ej2-react-dropdowns": "20.4.48",
"@syncfusion/ej2-react-grids": "20.4.48",
"@syncfusion/ej2-react-heatmap": "20.4.48",
"@syncfusion/ej2-react-inputs": "20.4.48",
"@syncfusion/ej2-react-pivotview": "20.4.48",
"@syncfusion/ej2-react-querybuilder": "20.4.48",
"@syncfusion/ej2-react-richtexteditor": "20.4.48",
"react": "^17.0.0",


The grid is set up to get the dataSource from the props, which changes on external events. My problem is that every time I update the props, the Grid after rerendering ti will emit the actionBegin event, too many times. This freezes the screen for a second.

table.PNG

The console log is like this:

GridComponent Render
index.jsx:99 actionBegin {requestType: 'refresh', name: 'actionBegin'}
index.jsx:99 actionBegin {requestType: 'refresh', name: 'actionBegin'}
index.jsx:99 actionBegin {requestType: 'refresh', name: 'actionBegin'}
index.jsx:99 actionBegin {requestType: 'refresh', name: 'actionBegin'}
index.jsx:99 actionBegin {requestType: 'refresh', name: 'actionBegin'}
index.jsx:99 actionBegin {requestType: 'refresh', name: 'actionBegin'}
...

Looking inside the log it looks like this:

So it seems to be recreating the same 6 rows multiple times... Any idea why this could be happening?

This is the setup:

render() {
console.log('GridComponent Render')

return (
<GridComponent
ref={g => (this.grid = g)}
dataSource={this.props.data}
allowPaging={true}
disablePageWiseAggregates={true}
allowGrouping={false}
allowSorting={true}
showColumnMenu={true}
allowExcelExport={true}
allowPdfExport={true}
allowFiltering={true}
groupSettings={{showGroupedColumn: true}}
pageSettings={{pageSize: 11}}
enableHover={true}
queryCellInfo={this.customizeCell}
showColumnChooser={true}
filterSettings={{type: 'Excel'}}
selectionSettings={{
mode: 'Row',
persistSelection: true,
type: 'Multiple',
checkboxMode: 'ResetOnRowClick',
}}
rowSelected={this.selectionModified}
rowDeselected={this.selectionModified}
allowSelection={true}
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>
)
}


actionBegin = args => {
console.log('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
}
}
}

4 Replies

RS Rajapandiyan Settu Syncfusion Team February 22, 2023 10:10 AM UTC

Hi GUU,

Thanks for contacting Syncfusion support.


To better understand the issue you're facing with the Grid, we would appreciate it if you could provide us with the following information:


  1. Complete code files of both parent component (where you have changed the dataSource) and child component (where you have defined the Grid).
  2. How could you update the dataSource in the parent component? Share the details.
  3. Are you updating multiple properties in the parent component?


This information will help us replicate the issue and provide a solution.


Regards,

Rajapandiyan S



GU GUU April 24, 2023 03:42 AM UTC

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)))



GU GUU May 15, 2023 12:31 AM UTC

Hi, Any updates on this?



RR Rajapandi Ravi Syncfusion Team June 6, 2023 02:09 PM UTC

Guu,


To prevent unnecessary re-renders of grandchild components when the state of a parent component changes, you can use React's React.memo (functional components) to memoize the components.


Memoization is a technique that allows React to optimize rendering by caching the result of a component's render based on its props. By memoizing a component, React will only re-render it if its props have changed.


Here's how you can prevent grandchild re-renders using memoization:


  • Import the memo function from React at the top of your file: import React, { memo } from 'react';


  • Wrap your grandchild component with memo before exporting it: export default memo(GrandchildComponent);


 

import React, { memo } from 'react';

 

const GrandchildComponent = ({ prop1, prop2 }) => {

  // Render the component...

};

 

export default memo(GrandchildComponent);

 


Also, please refer the below general discussion links for more information.


General links: https://stackoverflow.com/questions/55755207/how-to-prevent-child-component-re-render-when-parent-state-changes


                         https://stackoverflow.com/questions/40819992/react-parent-component-re-renders-all-children-even-those-that-havent-changed


Loader.
Up arrow icon