material-table-core / core

Datatable for React based on material-ui's table with additional features. Support us at https://opencollective.com/material-table-core
https://material-table-core.github.io
MIT License
295 stars 146 forks source link

Bulk update behaviour #104

Closed DewangS closed 3 years ago

DewangS commented 3 years ago

Guidelines

I have a functional component which uses MT's bulk update feature. Everything works as expected so, no issues there though... What I noticed is, after clicking the 'Edit' (Pencil Icon) button on the top right corner, all editable elements get enabled for the user input and user can update values in those elements. The issue is, user MUST click the tick (correct icon) in order to ACCEPT those changes, if user miss to ACCEPT those changes and move to the next\previous page or say click a command button on the page, user will end up losing their changes.

Is there any way to force user to ACCEPT or REJECT their changes when they perform activities like this? i.e. clicking any other button etc? I have a SAVE and CANCEL command buttons to save changes to the database via API, Ideally I would like to ACCEPT all outstanding changes on the page before running button's click handler.

DewangS commented 3 years ago

So, no one ran in to this issue? I really need some help to decide if I go ahead with bulk update or stay with just cell\row update .. it would be painful for the end user to perform 2 additional clicks per edit..

oze4 commented 3 years ago

You can most def do this but you have to add the logic yourself. We expose the needed props/etc.. to allow consumers to make this table highly customizable. If you could provide a sandbox that would be great.

DewangS commented 3 years ago

There isn't anything special in my code but here is a cutdown version of the code at code sandbox

steps to re-produce ....

  1. Try to click the Bulk Update 'pen' icon on the top right,
  2. Update couple of values on the first page
  3. DON'T click the check mark or the correct icon on the top right, just hit the next page icon in the bottom right corner
  4. now, again move back to the first page by clicking the 'previous page' icon on the top right
  5. and you will see your previous edits are now lost
Domino987 commented 3 years ago

The changes are not lost, but just hidden. And I just opened a PR to fix this.

DewangS commented 3 years ago

@Domino987 That's really odd. I am on v.2.3.26 and can definitely see that changes are not persisted. So, here is what I've observed,

I use Formik for the Form management and my MaterialTable is inside my Formik form. I have two buttons, one to cancel changes and return the user back to the home page and the second button to SUBMIT the Formik form. after updating a few values, when I click the SAVE command button which in turn makes an API call to SAVE the updates, I observe no changes to the updated cells..below are relevant code snippets ...

//initial declaration of JSON array to store and render data fetched using the API call from useEffect() method

const [applicationList, setApplicationList] = React.useState([]);

//the useEffect where I fetch data via API call

useEffect(() => {

    API.get("/api/ApplicationListByStatuses/27", {
      //signal: signal,
    })
      .then(function (response) {
        setApplicationList(response.data);
      })
      .catch(function (error) {
        console.log(error);
      });
}, []);

//below is form submit method where I update data using an API call. this is where the check on permit_number > 0 is always false.. see the if condition. I only need to submit if the value has been updated.

const submitForm = () => {
      let updatedApplicationRecords = []
      applicationList.forEach(application => {
          if (application.permit_number>0) {
              updatedApplicationRecords.push({
                  ApplicationID: application.application_id,
                  staffID: application.staff_id,
                  accessNumber: application.access_number,
                  appliactionStatusID: 2
              })
          }
      });
      console.log('+++ total records : ', updatedApplicationRecords)
      API.post("/api/Application/updateApplications", updatedApplicationRecords, {
        signal: signal,
      })
        .then(function (response) {
            console.log('+++ RESPONSE ', response)
            let status = response.status

            if (response.status===200) {
                console.log('+++ status', status)

              } else {

               console.log('Error occurred");
              }
            })
            .catch(function (error) {
              console.log("*** ERROR RESPONSE : ", error);
            });
          setTimeout(() => {
          }, 500);
  }

// below is my MT definition ..

<MaterialTable
              title="Update Applications"
              icons={tableIcons}
              columns={columns}
              data={applicationList}

              options={{
                showTitle: true,
                isLoading: true,
                pageSize: 10,

                headerStyle: {
                  backgroundColor: "#01579b",
                  color: "#FFF",
                }
              }}
              editable={{
                onBulkUpdate: (changes) =>
                  new Promise((resolve, reject) => {
                    setTimeout(() => {
                        const dataUpdate = [...applicationList];

                        for (var key in changes) {
                            if (changes.hasOwnProperty(key)) {

                              dataUpdate[key] = changes[key].newData
                            }
                        }
                        setApplicationList([...dataUpdate]);
                      resolve();
                    }, 1000);
                  })
              }}

            />

Unless I am doing something wrong in the MT onBulkUpdate method..

Domino987 commented 3 years ago

Hi, First of all, the PR I added should work for you anyway. But for your code, can you put that in a sandbox so I investigate it running, maybe with dummy data? Why would you put it in formik, if the table is tracking the data for you in the first place? Good you brought it up. I think this might be an error and i would like to get it fixed.

DewangS commented 3 years ago

I will try to add this to the original sandbox code. My sincere apology, I actually realised that I don't use Formik in this functional component. i.e. the below SAVE button actually triggers the earlier mentioned submitForm method. While there would you please verify my onBulkUpdate() and the conditional logic ..the below 2 lines.... hope I am doing it right here. Also, just curious, what even gets fired in response to the accept and cancel changes button clicks .. these 2 buttons appears as soon as user clicks on the Bulk Update 'pen' icon .. one with correct icon and the other with a cross icon on the top right corner...

applicationList.forEach(application => { if (application.permit_number>0) {

<Button
          variant="contained"
          size="medium"
          className={classes.buttonGreen}
          startIcon={<SaveIcon />}
          onClick={submitForm}
        >
oze4 commented 3 years ago

@DewangS I apologize, I misunderstood the issue at first but the sandbox cleared it up. Sorry I was not much help but @Domino987 seems to have this one under control.

Domino987 commented 3 years ago

@DewangS to not seeing them. I only opened a PR, so the code is neither merged nor releases. They will be with the next release though. Add the link to the new sandbox once you added the code, hit me up and i will check it out

DewangS commented 3 years ago

here is the updated code, you will see alert with zero every time if you follow my previous steps on how to reproduce... just update a few License Number values codeSandbox

DewangS commented 3 years ago

@oze4 No worries. I can understand that you guys are assisting us as much as you can on top of having your regular work. Much appreciated.

Domino987 commented 3 years ago

here is the updated code, you will see alert with zero every time if you follow my previous steps on how to reproduce... just update a few License Number values codeSandbox

Yes as mentioned, the values are not updated in the table itself during edit, but saving will still update the previously entered values. And the new PR, which just got merged will show the correct values on pagination. You still need to save to trigger the onBulkUpdate at the end. Add you do not need the ´setTimeout´ to artificial create a loading spinner btw.

DewangS commented 3 years ago

sorry but how do you manually trigger the onBulkUpdate? is there any way? i.e. say user updates one or more values and directly hit the save button which is a natural reaction on any web form, is there a way to accept the changes by manually triggering the onBulkUpdate?

Domino987 commented 3 years ago

Yes. You need to use the tableRef for that. Here is an example:

import React, { useState } from "react";
import { Button, Box, Grid } from "@material-ui/core";
import MaterialTable from "@material-table/core";
import SaveIcon from "@material-ui/icons/Save";
import CancelIcon from "@material-ui/icons/Cancel";
import { tableIcons } from "./material-table-icons";
import { green, red } from "@material-ui/core/colors";
const MTbulkUpdateTest = () => {
  const ref = React.useRef(null)
  const columns = [
    {
      field: "lastName",
      title: "Surname",
      cellStyle: {
        width: 150,
        minWidth: 150,
        whiteSpace: "nowrap"
      },
      headerStyle: {
        width: 150,
        minWidth: 150,
        whiteSpace: "nowrap"
      }
    },
    {
      field: "firstName",
      title: "Other Name",
      cellStyle: {
        width: 150,
        minWidth: 150,
        whiteSpace: "nowrap"
      },
      headerStyle: {
        width: 150,
        minWidth: 150,
        whiteSpace: "nowrap"
      }
    },
    {
      field: "licenseNo",
      title: "License Number",
      cellStyle: {
        width: 150,
        minWidth: 150,
        whiteSpace: "nowrap"
      },
      headerStyle: {
        width: 150,
        minWidth: 150,
        whiteSpace: "nowrap"
      }
    }
  ];
  const [applicationList, setApplicationList] = useState([
    { firstName: "Roger", lastName: "Grisham", licenseNo: 0 },
    { firstName: "Tracey", lastName: "Duckworth", licenseNo: 0 },
    { firstName: "Karina", lastName: "Fred", licenseNo: 0 },
    { firstName: "Bob", lastName: "Dillon", licenseNo: 0 },
    { firstName: "Frank", lastName: "Sinatra", licenseNo: 0 },
    { firstName: "Roger", lastName: "Grisham", licenseNo: 0 },
    { firstName: "Tracey", lastName: "Duckworth", licenseNo: 0 },
    { firstName: "Karina", lastName: "Fred", licenseNo: 0 },
    { firstName: "Bob", lastName: "Dillon", licenseNo: 0 },
    { firstName: "Frank", lastName: "Sinatra", licenseNo: 0 },
    { firstName: "George", lastName: "Song", licenseNo: 0 },
    { firstName: "Henry", lastName: "Luise", licenseNo: 0 },
    { firstName: "Lora", lastName: "Turnbull", licenseNo: 0 },
    { firstName: "Vicrtor", lastName: "Franko", licenseNo: 0 },
    { firstName: "Ashley", lastName: "Martin", licenseNo: 0 },
    { firstName: "Greg", lastName: "Hunt", licenseNo: 0 },
    { firstName: "Cathy", lastName: "Noel", licenseNo: 0 },
    { firstName: "Vicki", lastName: "Matrin", licenseNo: 0 },
    { firstName: "Jag", lastName: "Rogerson", licenseNo: 0 },
    { firstName: "Kim", lastName: "Kard", licenseNo: 0 }
  ]);

  const submitForm = () => {
    let counter = 0;
    Object.values(ref.current.dataManager.bulkEditChangedRows).forEach(({newData}) => {
      if (newData.licenseNo > 0) {
        counter++;
      }
    });
    alert("+++ Total Updated Records : " + counter);
  };

  return (
    <div>
      <Grid container direction="row" justify="center" alignItems="center">
        <Grid
          container
          item
          lg={12}
          md={12}
          xs={12}
          m={5}
          spacing={5}
          justify="space-between"
          style={{ marginTop: "2em" }}
        >
          <Box textAlign="center" m={2}>
            <MaterialTable
              title="Bulk Update Test"
              icons={tableIcons}
              columns={columns}
              tableRef={ref}
              data={applicationList}
              onChangePage={(event, rowData) => {
                console.log("++ changed page", event);
              }}
              options={{
                showTitle: true,
                isLoading: true,
                pageSize: 10,

                headerStyle: {
                  backgroundColor: "#01579b",
                  color: "#FFF"
                }
              }}
              editable={{
                onBulkUpdate: (changes) =>
                  new Promise((resolve, reject) => {
                    setTimeout(() => {
                      const dataUpdate = [...applicationList];

                      for (var key in changes) {
                        if (changes.hasOwnProperty(key)) {
                          dataUpdate[key] = changes[key].newData;
                        }
                      }
                      setApplicationList([...dataUpdate]);
                      resolve();
                    }, 1000);
                  }),
                onRowUpdate: (newData, oldData) =>
                  new Promise((resolve, reject) => {
                    setTimeout(() => {
                      const dataUpdate = [...applicationList];
                      const index = oldData.tableData.id;
                      dataUpdate[index] = newData;
                      setApplicationList([...dataUpdate]);

                      resolve();
                    }, 1000);
                  })
              }}
            />
          </Box>
        </Grid>
      </Grid>
      <Box textAlign="right" mx={15} my={3}>
        <Button
          variant="contained"
          color="secondary"
          size="medium"
          startIcon={<CancelIcon />}
          //disabled={isSubmitting}
          onClick={() => {
            //setConfirmOpen(true);
          }}
        >
          Cancel
        </Button>

        <Button
          variant="contained"
          color="primary"
          size="medium"
          startIcon={<SaveIcon />}
          onClick={submitForm}
        >
          Save
        </Button>
      </Box>
    </div>
  );
};

export default MTbulkUpdateTest;
DewangS commented 3 years ago

Perfect! Thanks @Domino987 I just tried this in my sandbox and it worked. Much appreciated.

DewangS commented 3 years ago

ooops.. Houston we have a problem. I was bit to excited to report the success. Just tried other way round i.e. updated a few values though this time around 'ACCEPTED' my changes on each page then hit the 'SAVE' button and and now getting 0 rows updated. I guess I have check both... see below... i.e. the Ref and the applicationList in my submitForm(). Am I right?

const submitForm = () => {
    let counter = 0;
    Object.values(ref.current.dataManager.bulkEditChangedRows).forEach(
      ({ newData }) => {
        if (newData.licenseNo > 0) {
          counter++;
        }
      }
    );
    applicationList.forEach(
      (application) => {
        if (application.licenseNo > 0) {
          counter++;
        }
      }
    );
    alert("+++ Total Updated Records : " + counter);
  };
Domino987 commented 3 years ago

Depends on what you need to do, Changes/bulkEditChangedRows contains only changed fields. SO if you need all above 0, you would need to check both yes.