'material-table after open dialog in edit row is closed edit of row

I use in Reactjs library material-table. I have for one column button for add image using dialog. It work good for add new row but if i want edit row after open dialog is closed edit of row. I think that problem can be cause with rerender.

Open edit row enter image description here

After click at button is opened dialog for add image but edit of row is ended and i cant save change. enter image description here

import { Button, Container, IconButton } from "@material-ui/core";
import MaterialTable from "@material-table/core";
import React, { useState } from "react";
import { DropzoneDialogBase } from "material-ui-dropzone";
import CloseIcon from "@material-ui/icons/Close";
import { tableIcons } from "./IconProvider";

const App = () => {
  const [equipment, setEquipment] = useState([{url: '', name: 'Test'}]);
  const [open, setOpen] = useState(false);
  const [image, setImage] = useState([]);

  const FILES_LIMIT = 1;
  const pageSize = [10, 20, 50, 100];

  const dialogTitle = () => (
    <>
      <span>Upload file</span>
      <IconButton
        style={{ right: "12px", top: "8px", position: "absolute" }}
        onClick={() => setOpen(false)}
      >
        <CloseIcon />
      </IconButton>
    </>
  );

  return (
    <Container maxWidth={"xl"}>
      <MaterialTable
        columns={[
          {
            field: "url",
            title: "Url",
            editComponent: () => (
              <Button
                variant="contained"
                color="primary"
                onClick={() => setOpen(true)}
              >
                {"Add image"}
              </Button>
            ),
            render: (rowData) => <img src={rowData.url} />
          },
          { field: "name", title: "Name" }
        ]}
        data={equipment}
        title={"Title"}
        options={{
          pageSizeOptions: pageSize,
          pageSize: pageSize[0]
        }}
        icons={tableIcons}
        editable={{
          onRowAdd: (newData) =>
            new Promise((resolve, reject) => {
              setTimeout(() => {
                setEquipment([...equipment, newData]);

                resolve();
              }, 1000);
            }),
          onRowUpdate: (newData, oldData) =>
            new Promise((resolve, reject) => {
              setTimeout(() => {
                const dataUpdate = [...equipment];
                const index = oldData.tableData.id;
                dataUpdate[index] = newData;
                setEquipment([...dataUpdate]);

                resolve();
              }, 1000);
            }),
          onRowDelete: (oldData) =>
            new Promise((resolve, reject) => {
              setTimeout(() => {
                const dataDelete = [...equipment];
                const index = oldData.tableData.id;
                dataDelete.splice(index, 1);
                setEquipment([...dataDelete]);

                resolve();
              }, 1000);
            })
        }}
      />

      <DropzoneDialogBase
        dialogTitle={dialogTitle()}
        acceptedFiles={["image/*"]}
        fileObjects={image}
        cancelButtonText={"cancel"}
        submitButtonText={"submit"}
        maxFileSize={5000000}
        filesLimit={FILES_LIMIT}
        open={open}
        onAdd={(newFileObjs) => {
          setImage([].concat([], newFileObjs));
        }}
        onDelete={(deleteFileObj) => {
          setImage(image.filter((item) => item !== deleteFileObj));
        }}
        onClose={() => setOpen(false)}
        onSave={() => {
          setOpen(false);
        }}
        showPreviews={true}
        showFileNamesInPreview={true}
      />
    </Container>
  );
};

export default App;

I create examle at Codesandbox



Solution 1:[1]

You are correct, issue happen since you are trigger rerender on each state update, this happen when the columns provided to material table are static, but contain functions which update on every render, resetting the table state. Provide a stable function or column reference or an row id to prevent state loss.

So, you fix is:

  const preventRerender = useCallback((rowData) => <img src={rowData.url} />, []);
  const editComponent = useCallback(() => {
    return <Button
      variant="contained"
      color="primary"
      onClick={() => setOpen(true)}
    >
      {"Add image"}
    </Button>;
  }, [])

<MaterialTable
        columns={[
          {
            field: "url",
            title: "Url",
            editComponent,
            render: preventRerender
          },
          { field: "name", title: "Name" }
        ]}

by adding callback or useMemo you will prevent update state else if you want by update array dependency.

Additional note, keep track and take care about rows key/id

Sandbox Demo

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Anees Hikmat Abu Hmiad