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>
)}
</>
);
};