Performance issue

Tags: #<Tag:0x00007efc6b421fd0> #<Tag:0x00007efc6b421d50>

I have a custom cell renderer in a Handsontable table. Whenever I edit the cell, the custom cell renderer is triggered multiple times, as shown in this video. Specifically, the cell renders 15 * 3 = 45 times. The tech stack I’m using in this sample includes React and Redux. The version of Handsontable is v14.5.0. Below is the code snippet. How can I reduce the re-rendering?

import "handsontable/dist/handsontable.full.min.css";
import { HotTable, HotColumn } from "@handsontable/react";
import { registerAllModules } from "handsontable/registry";
import {useDispatch} from "react-redux";
import * as TaskAction from "src/redux/actions/TaskAction";
import Handsontable from "handsontable";
import "./handsongrid.css";

// register Handsontable's modules
registerAllModules();

function HandsOnGrid({ data, readOnly=false }) {
  const dispatch = useDispatch();
  const { taskList } = data;
  
  const createRows = () => {
    let rows = [];
    const tmpTaskList = [...taskList];

    tmpTaskList.forEach((large, lIndex) => {
      rows.push({
        ltask: large.taskName,
        mtask: "AA",
        stask: "AAA",
        ltaskId: -1,
        mtaskId: -2,
        staskId: -3,
        unitTask: false,
      });
    });

    return rows;
  };

  const rows = createRows();

  const afterChange = (changes, source) => {
    if (source !== "loadData") {

      changes !== null && changes.forEach(([row, prop, oldVal, newVal]) => {
        const ltaskId = rows[row]["ltaskId"];
        const mtaskId = rows[row]["mtaskId"];
        const unitTask = rows[row]["unitTask"];
        console.log("afterChange", ltaskId, mtaskId, unitTask, newVal);
        switch (prop) {
          case "ltask":
            handleTaskNameChange(unitTask, ltaskId, mtaskId, ltaskId, prop, newVal);
            break;
          default:
            break;
        }
      });
    }
  };

  const dispatchRowValue = (taskId, column, value) => {
    dispatch({
      type: TaskAction.TASKLISTREVISED_SET_ROW_VALUES,
      payload: {
        index: taskId,
        name: column,
        value: value
      },
    });
  };

  const handleTaskNameChange = (unitTask, ltaskId, mtaskId, targetTaskId, taskType, value) => {
    if (unitTask === false || (unitTask === true && taskType === "ltask")) {
      dispatchRowValue(targetTaskId, "taskName", value === null ? "" : value);
    }
  };

  const taskNameCell = function (hotInstance, td, row, col, prop, value, cellProperties) {
    console.log("### taskNameCell ###");
    Handsontable.renderers.TextRenderer.apply(this, arguments);
    td.innerHTML = value;
    if(readOnly){
      td.style.color = "#999";
      td.style.background = "#DADADA";  // cell's background color
      cellProperties.readOnly = true; // Disable the cell
    }

    return td;
  };


  Handsontable.renderers.registerRenderer("taskNameCell", taskNameCell);

  return (
    <div>
      <HotTable
        data={rows}
        manualColumnResize={true}
        autoRowSize={false}
        autoColumnSize={false}
        licenseKey="xxxx"
        afterChange={afterChange}
      >
        <HotColumn data="ltask" width={170} renderer={taskNameCell} />
      </HotTable>
    </div>
  );
}

export default HandsOnGrid;

Hi @jason1

I checked an example with a single custom renderer and even though the table gets re-rendered with each action, I didn’t observe as many events as you mentioned. Can you please modify this example so the issue can be replicated in it? https://jsfiddle.net/handsoncode/xrecbwL6/

I made the sample simpler. An object is stored in Redux, and when I edit a cell, the UPDATE_TASK action is called to store the updated value in Redux. The Handsontable component then retrieves the latest value through const tasks = useSelector((state) => state.TestHandsontableReducer.tasks); and displays it in the grid. However, the taskNameCell still re-renders five times in each update.

Handsontable component:
import “handsontable/dist/handsontable.full.min.css”;
import { HotTable } from “@handsontable/react”;
import { registerAllModules } from “handsontable/registry”;
import {useDispatch, useSelector} from “react-redux”;
import {UPDATE_TASK} from “src/redux/actions/TestHandsontableAction”;

// register Handsontable's modules
registerAllModules();

function HandsOnGrid() {
  const dispatch = useDispatch();
  const tasks = useSelector((states) => (states.TestHandsontableReducer.tasks));

  const afterChange = (changes, source) => {
    if (source !== "loadData") {

      changes !== null && changes.forEach(([row, prop, oldVal, newVal]) => {
        switch (prop) {
          case "ltask":
            dispatch({
              type: UPDATE_TASK,
              payload: {
                ltask: newVal
              },
            });
            break;
          default:
            break;
        }
      });
    }
  };

  function taskNameCell (hotInstance, td, row, col, prop, value, cellProperties) {
    console.log("### taskNameCell ###");
    td.innerHTML = value;
    return td;
  }

  return (
    <div>
      <HotTable
        id="hot"
        data={JSON.parse(JSON.stringify(tasks))}
        columns={[
          { data: "ltask", renderer: taskNameCell },
        ]}
        colHeaders={true}
        rowHeights={28}
        height="auto"
        autoWrapRow={true}
        autoWrapCol={true}
        licenseKey=""
        afterChange={afterChange}
      />
    </div>
  );
}

export default HandsOnGrid;

Redux:
import produce from “immer”;
import {UPDATE_TASK} from “src/redux/actions/TestHandsontableAction”;

const initialState = {
  tasks: [{ ltask: "Good" }]
};

const TestHandsontableReducer = (state = initialState, action) => 
  produce(state, draft => {
    switch (action.type) {
      case UPDATE_TASK:
        draft.tasks[0]["ltask"] = action.payload.ltask;
        break;
      default:
        // Do not blow things up :3
        break;
    }
  });

export default TestHandsontableReducer;

Hi @jason1

Can you please use my example and modify it with your code? There are some missing imports, and I’m not able to get the same results. Thank you.

Hi @jason1

This thread has been inactive for an extended period, so I’m closing it. If you’re still experiencing the issue or need further assistance, feel free to create a new thread.

Thanks for understanding.