react-table v8 how to prevent cell from rerender

Viewed 49

How in react-table to avoid cell rerender when changing data? In simple terms: each cell is an input, onBlur a patch to the backend is fired and again the data is retrieved from the server. It seems that only the cells with my custom component are being re-rendered, and, of course, the whole table, which is probably intended.

A basic table component built according to the examples from the official react-table documentation. Only added scrolling ScrollArea and styling from Table.

import React from "react";

import {
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table";
import { Table } from "@mantine/core";
import { ScrollArea } from "@mantine/core";

const TableModel = ({ data, columns }) => {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
  });

  return (
    <ScrollArea style={{ height: "50vh" }}>
      <Table style={{ position: "relative" }}>
        <thead
          style={{ position: "sticky", top: "0", backgroundColor: "black" }}
        >
          {table.getHeaderGroups().map((headerGroup) => (
            <tr key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <th key={header.id}>
                  {header.isPlaceholder
                    ? null
                    : flexRender(
                        header.column.columnDef.header,
                        header.getContext()
                      )}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody>
          {table.getRowModel().rows.map((row) => (
            <tr key={row.id}>
              {row.getVisibleCells().map((cell) => (
                <td key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </Table>
    </ScrollArea>
  );
};

export default TableModel;

Here I defined the columns and downloaded the data from the server. IsLoading fires only on the first render.

import React from "react";
import TableModel from "./TableModel";
import { createColumnHelper } from "@tanstack/react-table";
import { v4 as uuidv4 } from "uuid";
import { usePZItemsData } from "../hooks/usePZItemsData";
import { CustomInputHover } from "./CustomInputHover";

export const ItemList = ({ id }) => {
  const { data: pzItemsData, isLoading } = usePZItemsData(id);

  const columnHelper = createColumnHelper();

  const columns = React.useMemo(
    () => [
      columnHelper.accessor("id"),
      columnHelper.accessor("product.name", { header: "Nazwa" }),
      columnHelper.accessor("quantity", {
        header: "Nazwa",
        cell: (i) => <CustomInputHover i={i} />,
      }),
    ],
    []
  );

  return (
    <>
      {isLoading ? (
        <h1>Loading...</h1>
      ) : (
        <TableModel data={pzItemsData?.data} columns={columns} />
      )}
    </>
  );
};

Custom input cell. Using input or input from the framework slowed down the rendering terribly. I used this solution, which reduces the rerender time drastically, but it is still long. In short, when you click on a div, a Input appears and then its validation.

import { NumberInput, TextInput } from "@mantine/core";
import { usePrevious } from "@mantine/hooks";
import React, { useEffect } from "react";
import { useUpdatePZItem } from "../hooks/usePZItemActions";

export const CustomInputHover = ({ i }) => {
  const [clicked, setClicked] = React.useState(false);
  const [value, setValue] = React.useState();
  const previousValue = usePrevious(value);

  const { mutate, isError, isSuccess } = useUpdatePZItem();

  useEffect(() => {
    setValue(i.row.original.quantity);
  }, [i.row.original.quantity]);

  const handleBlur = (e) => {
    if (previousValue != value) {
      mutate({
        id: i.row.original.id,
        data: { quantity: value },
      });
    } else {
      if (!isError) {
        setClicked(false);
      }
    }
  };

  React.useEffect(() => {
    if (isSuccess) {
      setClicked(false);
    }
  }, [isSuccess]);

  React.useEffect(() => {
    if (isError) {
      setClicked(true);
    }
  }, [isError]);

  const handleOnKeyDown = (e) => {
    if (e.key !== "Tab" && e.key !== "Shift" && e.key !== "Alt") {
      setClicked(true);
      console.log(e);
    }
  };

  return (
    <>
      {clicked ? (
        <NumberInput
          style={{ width: "180px" }}
          autoFocus
          value={value}
          size="xs"
          radius="xs"
          variant="filled"
          onBlur={(e) => handleBlur(e)}
          async
          error={isError && "Podaj poprawną wartość"}
          onFocus={(e) => e.target.select()}
          onChange={(val) => setValue(val)}
        />
      ) : (
        <div
          onClick={() => setClicked(true)}
          tabIndex={0}
          onKeyDown={(e) => handleOnKeyDown(e)}
        >
          {i.row.original.quantity}
        </div>
      )}
    </>
  );
};
0 Answers
Related