reactjsreduxredux-toolkittabulator

react-tabulator Table component throws an error when passing data by redux state


I am facing issues with react-tabulator library in ReactJS. when i try to use the ReactTabulator component and pass the data props value (which i have to get from redux store) the component throws an error ("MachineID" is read-only) [See ScreenShot for more detail]. i have tried TanStack table component as well and it works perfectly so i am not sure what i am doing wrong here!

Redux Libraries :

"react-redux": "^9.1.0"
"redux": "^5.0.1"
"@reduxjs/toolkit": "^2.2.1"

My Code:

import * as React from "react";

import { useSelector } from "react-redux";

import "react-tabulator/lib/styles.css"; // required styles
import "react-tabulator/lib/css/tabulator.min.css"; // theme
import { ReactTabulator } from "react-tabulator";
import { useEffect, useState } from "react";

const CostSheet = () => {
  // TRY ONE
  const { basic_costsheet } = useSelector((state) => state.qm.costsheet);

  // TRY TWO
  const [data, setData] = useState(basic_costsheet);

  // TRY THREE
  const mod_arr = basic_costsheet.map((e) => e);

  // TRY FOUR
  const mod_arr_two = [];
  basic_costsheet.forEach((element) => {
    mod_arr_two.push(element);
  });

  // TRY FIVE
  const cs_data = [
    {
      McID: 1,
      MachineID: "1",
      MachineName: "m1",
      MaxLength: 1020,
    },
    {
      McID: 2,
      MachineID: "2",
      MachineName: "m2",
      MaxLength: 1050,
    },
    {
      McID: 3,
      MachineID: "3",
      MachineName: "m3",
      MaxLength: 1050,
    },
    {
      McID: 4,
      MachineID: "41",
      MachineName: "m4",
      MaxLength: 1060,
    },
  ];

  const columns = [
    { title: "MachineID", field: "MachineID" },
    { title: "MachineName", field: "MachineName" },
    { title: "MaxLength", field: "MaxLength" },
  ];

  const options = {
    layout: "fitColumns",
    pagination: "local",
    paginationSize: 10,
    movableColumns: true,
    resizableRows: true,
    initialSort: [{ column: "MachineID", dir: "asc" }],
  };

  return (
    <div>
      <ReactTabulator
        columns={columns}
        data={basic_costsheet} // TRY FIVE works as i pass static data or set data directly in useState.
        options={options}
      />

      {/* Every variable/state/redux state gets printed perfectly so the data is loading (IF AVAILALBE). no issues on that front! */}
      <pre>{String(JSON.stringify(basic_costsheet))}</pre>
      <pre>{String(JSON.stringify(data))}</pre>
      <pre>{String(JSON.stringify(mod_arr))}</pre>
      <pre>{String(JSON.stringify(mod_arr_two))}</pre>
      <pre>{String(JSON.stringify(cs_data))}</pre>
    </div>[![enter image description here][1]][1]
  );
};

export default CostSheet;

enter image description here


Solution

  • The issue seems to be that ReactTabulator is mutating the data array objects in some way and this breaks when using values directly from a Redux-Toolkit (RTK) created store. This is because RTK uses Immer.js under the hood and enforces that selected state is read-only. In other words, so you don't shoot yourself in the foot by mutating state.

    You can simply create new data object references that are not directly coupled to your Store values though. Shallow copy the basic_costsheet array and shallow copy each array element.

    Examples:

    const { basic_costsheet } = useSelector((state) => state.qm.costsheet);
    
    ...
    
    <ReactTabulator
      columns={columns}
      data={basic_costsheet.map(item => ({ ...item }))}
      options={options}
    />
    
    const basic_costsheet = useSelector((state) => state.qm.costsheet
      .basic_costsheet
      .map(item => ({ ...item }))
    );
    
    ...
    
    <ReactTabulator
      columns={columns}
      data={basic_costsheet}
      options={options}
    />