antd model loads very slow, and in antd table all operation sorting, filtering and pagination changes are also slow for large data set

Viewed 1005

On dataView true dataViewModel is rendered

{popUps.dataView && popupsFactory.dataView}

Rendering of model is very slow, which have and table with dataSource around 10k-20k or more.

/* eslint-disable react/prop-types */
/* eslint-disable indent */
/* eslint-disable linebreak-style */

import React, { useState, useEffect, useRef } from 'react'
import { Modal, Select, Table, Input, Button, Icon, Tooltip } from 'antd'
//import Highlighter from 'react-highlight-words'
import { connect } from 'react-redux'

import actionDispatcher from 'client/app-actions'
import { isFiltered } from 'client/routes/app/constants/deck-layers'
//import TableLayout from './Table'
import styled from './styled-component'
//import './style.css'

const { Option } = Select
const dataViewModal = (props) => {
    const {
        visibility
    } = props
    const [layerId, setLayerId] = useState('')
    const [tableColumns, setTableColumns] = useState(null)
    const [tableData, setTableData] = useState(null)
    const [searchText, setSearchText] = useState('')
    const [searchedColumn, setSearchedColumn] = useState('')
    const [filteredInfo, setFilteredInfo] = useState(null)
    const [sortedInfo, setSortedInfo] = useState(null)
    const [pagination, setPagination] = useState({ pageSize: 20, current: 1 })
    const [currentDataSource, setCurrentDataSource] = useState(null)
    const searchInput = useRef(null)
    useEffect(() => {
     // console.log(currentDataSource)
    }, [currentDataSource])
    /**
     * initialize layer
     */
    useEffect(() => {
        setLayerId(props.layers.length ? props.layers[0].uniqueKey : null)
    }, [props.layers])
    /**
     * whenerver layerId changes clear the sort and filter for the columns
     */
    useEffect(() => {
      clearAll()
    }, [layerId])
    const tempLayer = props.layers.find(item => item.uniqueKey === layerId)
    useEffect(() => {
        const layer = props.layers.find(item => item.uniqueKey === layerId)
        if (layer?.props?.coordinates) {
            showMapDataToUser(layer, false)
        } else {
            setTableColumns(null)
            setTableData(null)
        }
    }, [tempLayer, sortedInfo, filteredInfo])
    const showMapDataToUser = (layer, exportToCsv) => {
        const filteredColumnSet = layer.columnSet.filter(
            item => item.variableAdded
        )
        const showDataToUser = []
        const showColumns = []
        filteredColumnSet.forEach((item, index) => {
            if (item.showToUser) {
                showColumns.push(`${item.displayName}`)
            }
        })
        showDataToUser.push([showColumns])
        layer.props.coordinates.forEach((it) => {
            const showData = []
            filteredColumnSet.forEach((item, index) => {
                if (item.showToUser) {
                    if (it.geometry &&
                        it.geometry.data[layer.props.columnSet + isFiltered]) {
                        if (showColumns.includes('Network GMP')) {
                            if (item.columnName.toUpperCase() === 'OLYMPUS_ID') {
                                const splitLatLong = it.geometry.data[index].split('_')
                                if (splitLatLong.length > 2
                                    && !isNaN(splitLatLong[splitLatLong.length - 1])
                                    && !isNaN(splitLatLong[splitLatLong.length - 2])) {
                                    splitLatLong.splice(-2, 2)
                                }
                                const splitLatLongString = splitLatLong.join('_')
                                showData.push(`${splitLatLongString}`.replace(/,/g, ';'))
                            } else if (item.columnName.toUpperCase() === 'RETAINABILITY_KPI' && !isNaN(it.geometry.data[index])) {
                                const calculativeDCR = 100 - (it.geometry.data[index])
                                showData.push(`${calculativeDCR}`.replace(/,/g, ';'))
                            } else {
                                showData.push(`${it.geometry.data[index]}`.replace(/,/g, ';'))
                            }
                        } else {
                            showData.push(`${it.geometry.data[index]}`.replace(/,/g, ';'))
                        }
                    } else if (it[layer.props.columnSet + isFiltered]) {
                        if (showColumns.includes('Network GMP')) {
                            if (item.columnName.toUpperCase() === 'OLYMPUS_ID') {
                                const splitLatLong = it[index].split('_')
                                if (splitLatLong.length > 2
                                    && !isNaN(splitLatLong[splitLatLong.length - 1])
                                    && !isNaN(splitLatLong[splitLatLong.length - 2])) {
                                    splitLatLong.splice(-2, 2)
                                }
                                const splitLatLongString = splitLatLong.join('_')
                                showData.push(`${splitLatLongString}`.replace(/,/g, ';'))
                            } else if (item.columnName.toUpperCase() === 'RETAINABILITY_KPI' && !isNaN(item[index])) {
                                const calculativeDCR = 100 - (item[index])
                                showData.push(`${calculativeDCR}`.replace(/,/g, ';'))
                            } else {
                                showData.push(`${it[index]}`.replace(/,/g, ';'))
                            }
                        } else {
                            showData.push(`${it[index]}`.replace(/,/g, ';'))
                        }
                    }
                }
            })
            if (showData.length) { 
                showDataToUser.push([showData]) 
            }
        })
  //-------------------------------------------
       if (!exportToCsv) {
        //create table data
        const columnNames = filteredColumnSet.filter(col => col.showToUser).map(col => col.columnName)
        const data = showDataToUser.slice(1).map((dataSet, index) => {
            //items[0]: array converted to obj
            const itemsObj = { key: index + 1 }
            dataSet[0].forEach((item, i) => {
                itemsObj[columnNames[i]] = item
            })
            return itemsObj
        })
        // console.log(data)
        setTableData(data)
        //
        //create table columns
        const sortedInfoNew = sortedInfo || {}
        const filteredInfoNew = filteredInfo || {}
        const columns = []
        columns.push({
            title: 'No.',
            dataIndex: 'key',
            width: 70,
           // fixed: 'left',
            key: 'key',
        })
        const len = filteredColumnSet.filter(col => col.showToUser).length
        filteredColumnSet.filter(col => col.showToUser).forEach((item, index) => {
            if (item.showToUser) {
                // console.log(item)
                const columnObj = index === 0 ? ({
                    title: item.displayName,
                    width: 150,
                   // fixed: 'left',
                    displayName: item.displayName,
                    dataIndex: item.columnName,
                    key: item.columnName,
                    sorter: item.columnName ? (a, b) => sortByColumnType(a, b, item) : null,
                    sortDirections: ['descend', 'ascend'],
                    sortOrder: sortedInfoNew.columnKey === item.columnName && sortedInfoNew.order,
                    filteredValue: filteredInfoNew[item.columnName] || null, // to clear search
                    ...getColumnSearchProps(item.columnName)
                })
                    : index === len - 1 ?
                    ({
                        title: () => columnTitle(item, data),
                        width: 150,
                        //fixed: 'right',
                        displayName: item.displayName,
                        dataIndex: item.columnName,
                        key: item.columnName,
                        filters: getFilters(data, item.columnName),
                        sorter: item.columnName ? (a, b) => sortByColumnType(a, b, item) : null,
                        onFilter: (value, record) => record[item.columnName].indexOf(value) === 0,
                        sortDirections: ['descend', 'ascend'],
                        sortOrder: sortedInfoNew.columnKey === item.columnName && sortedInfoNew.order,
                        filteredValue: filteredInfoNew[item.columnName] || null,
                    }) :
                    ({
                        title: () => columnTitle(item, data),
                        width: 150,
                        displayName: item.displayName,
                        dataIndex: item.columnName,
                        key: item.columnName,
                        filters: getFilters(data, item.columnName),
                        sorter: item.columnName ? (a, b) => sortByColumnType(a, b, item) : null,
                        onFilter: (value, record) => record[item.columnName].indexOf(value) === 0,
                        sortDirections: ['descend', 'ascend'],
                        sortOrder: sortedInfoNew.columnKey === item.columnName && sortedInfoNew.order,
                        filteredValue: filteredInfoNew[item.columnName] || null,
                    })
                columns.push(columnObj)
            }
        })
       // console.log(columns)
        setTableColumns(columns)
        //
    }
 //-------------------------------------------
        // console.log(showDataToUser)
        return showDataToUser
    }
    const getFilters = (data, columnName) => {
        const uniqueValuesSet = new Set(data.map(dataObj => dataObj[columnName]))
        const filters = Array.from(uniqueValuesSet).map(value => ({ text: value, value }))
        return filters
    }
    const sortByColumnType = (a, b, item) => {
        if (item.columnType === 'NUMERIC') {
            return a[item.columnName] - b[item.columnName]
        } else if (item.columnType === 'TEXT') {
            if (!a[item.columnName]) {
             // console.log(item, a)
            }
            return a[item.columnName]?.localeCompare(b[item.columnName])
        }
       // console.log(item)
        return null
    }
    /**
     * set to initial values
     */
   const clearAll = () => {
        setFilteredInfo(null)
        setSortedInfo(null)
        setPagination({
            pageSize: 20,
            current: 1
        })
        setCurrentDataSource(null)
      }
    function getColumnSearchProps(dataIndex) {
        return {
            filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
              <div style={{ padding: 8 }}>
                <Input
                  ref={searchInput}
                  placeholder={`Search ${dataIndex}`}
                  value={selectedKeys && selectedKeys[0]}
                  onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
                  onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
                  style={{ width: 188, marginBottom: 8, display: 'block' }}
                />
                <Button
                  type="primary"
                  onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
                  icon="search"
                  size="small"
                  style={{ width: 90 }}
                >
                  Search
                </Button>
                <Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
                  Reset
                </Button>
              </div>
            ),
            filterIcon: filtered => <Icon type="search" style={{ color: filtered ? '#1890ff' : undefined }} />,
            onFilter: (value, record) =>
                record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
            onFilterDropdownVisibleChange: (visible) => {
                if (visible) {
                    setTimeout(() => searchInput.current.select())
                }
            },
            //   render: text =>
            //     searchedColumn === dataIndex ? (
            //       <Highlighter
            //         highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
            //         searchWords={[searchText]}
            //         autoEscape
            //         textToHighlight={text.toString()}
            //       />
            //     ) : (
            //       text
            //     ),
        }
    }

    function handleSearch(selectedKeys, confirm, dataIndex) {
        confirm()
        setSearchText(selectedKeys[0])
        setSearchedColumn(dataIndex)
    }

    function handleReset(clearFilters) {
        clearFilters()
        setSearchText('')
    }

    const handleCancel = () => {
        props.updatePopUpMode('dataView')
    }
    const handleChange = (value) => {
        // console.log(`selected ${value}`)
        setLayerId(value)
    }
    function onChange(pager, filters, sorter, extra) {
        //  console.log(pager, filters, sorter, extra)
          setFilteredInfo(filters)
          setSortedInfo(sorter)
          setPagination(pager)
          setCurrentDataSource(extra.currentDataSource)
    }
   const handleDownloadLayer = () => {
        //const layer = props.layers.find(item => item.uniqueKey === layerId)
        if (tableColumns?.length) {
        //export in csv
        const showDataToUser = []
        showDataToUser.push(tableColumns.map(({ displayName }) => displayName).slice(1)) // don't include key column ref: line 138
        const dataSource = currentDataSource || tableData
        showDataToUser.push(...dataSource.map(item => tableColumns.map(({ key }) => (item[key])).slice(1))) // don't include key column values ref: line 138
        let csvContent = ''
        showDataToUser.forEach((row) => {
          csvContent += row.join(',')
          csvContent += '\n'
        })
        const encodedUri = URL.createObjectURL(new Blob([csvContent], { type: 'text/plain' }))
        const a = document.createElement('a')
        a.href = encodedUri
        a.target = '_Blank'
        a.download = 'mapdata.csv'
        a.click()
        //export in csv
        }
      }
    const selectedLayer = props.layers.find(layer => layer.uniqueKey === layerId)
    const columnTitle = (item, data) => {
        const columnData = data.map(dataObj => dataObj[item.columnName])
       // console.log(columnData)
        const nullColumnData = columnData.filter(value => value === 'null')
        const nullPercent = (nullColumnData.length / columnData.length) * 100
        return (
          <div>
            <Tooltip placement="top" title={item.displayName}>
              <styled.columnDisplayName>
                {item.displayName}
              </styled.columnDisplayName>
            </Tooltip>
            <div style={{ fontSize: '10px', color: '#ff5c00a1' }}>
              {Math.round(nullPercent)}% null rows
            </div>
          </div>
           )
    }
    const LayerNames = () => (
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Select defaultValue={selectedLayer?.uniqueKey} style={{ width: 120 }} onChange={handleChange}>
          {
                    props.layers.map(item =>
                      <Option value={item.uniqueKey}>{item.displayName}</Option>
                    )

                }
        </Select>
      </div>
    )
    return (
      <styled.modelWrapper
        width="98%"
        style={{ top: '20px' }}
        title={<LayerNames/>}
        visible={visibility}
        onCancel={handleCancel}
        footer={null}
      >
        <div className="Model Content" style={{ height: '590px' }}>
          <styled.modalContentHeader>
            <div style={{ float: 'right' }}>
              <Button onClick={handleDownloadLayer}>Export to CSV</Button>
            </div>
          </styled.modalContentHeader>
          <styled.tableWrapper style={{
                        overflow: 'auto',
                        height: '88%'
                    }}
          >
            {!tableData?.length ?
                            'NO DATA'
                            :
                            <styled.table
                              className="table-striped-rows"
                              columns={tableColumns}
                              dataSource={tableData}
                              pagination={pagination}
                              size="small"
                              onChange={onChange}
                              scroll={{ y: 410, x: 1340 }}
                              rowClassName={(record, index) => (index % 2 === 0 ? 'table-row-light' : 'table-row-dark')}
                            />
                        }
          </styled.tableWrapper>
        </div>
      </styled.modelWrapper>
    )
}

const mapStateToProps = (store, ownProps) => {
    const appViewState = store.appStore.appView
    const layers = store.layerStore.layers
    return {
        selectedLayer: layers.length && layers.find(it => it.id === appViewState.variableSelectLayerId),
        visibility: appViewState.popups.dataView,
        layers
    }
}

const mapDispatchToProps = {
    updatePopUpMode: actionDispatcher.updatePopUpMode,
    updateCoordinateDataFilter: actionDispatcher.updateCoordinateDataFilter
}

const DataView = connect(
    mapStateToProps,
    mapDispatchToProps
)(dataViewModal)

export default DataView

All operations like sorting, filtering, and pagination change are very slow. Filters take too long to render. Is there something I can do to optimize code. I tried to do calculation part of table dataSource before rendering and then passing it as a prop from Redux store, but that didn't work either.

0 Answers
Related