2

I am building an application that will request data from an API and display it in an editable table, where the user can edit and update the data base. I am using React with material-ui and material-table.

I will initialize the data in the state of the parent component, and pass it as props to the child component that renders the table. For test purposes, I initialize the data in the state to simulate later implementation of props. The table renders correctly, but when I edit, the values don't change.

export default function Table(props){
  const [gridData, setGridData] = useState({
    data: [
      { param: "Admin", val: "0.03" },
      { param: "Margin", val: "0.4" },
      { param: "Price", val: "5080" },
    ],
    resolve: () => {}
  });
  useEffect(() => {
    gridData.resolve();
  }, [gridData]);

  const onRowUpdate = (newData, oldData) =>
    new Promise((resolve, reject) => {
      const { data } = gridData;
      const index = data.indexOf(oldData);
      data[index] = newData;
      setGridData({ ...gridData, data, resolve });
    });

  const { data } = gridData;
  return (
    <div>
      <MaterialTable
      columns={props.col}
      data={data}
      editable={{
        isEditable: rowData => true,
        isDeletable: rowData => true,
        onRowUpdate: onRowUpdate
      }}
      />
    </div>
  );
}

Now, I found that the table works properly when I replace the columns={props.col} line with this:

columns={[
        { title: 'Parameters', field: 'param', editable: 'never' },
        { title: 'Value', field: 'val', editable: 'onUpdate' }
      ]}

So it appears that my problem is with the columns and not the data.

Any help would be greatly appreciated!

NOTE: the code is based on this response from github: https://github.com/mbrn/material-table/issues/1325

EDIT: The columns are passed from the parent component like this:

const comonscol = [
  { title: 'Parameters', field: 'param', editable: 'never' },
  { title: 'Value', field: 'val', editable: 'onUpdate' }
];

export default function ParamsSection(props) {
    ...
    return (
        <div>
            ...
            <Table col={comonscol} data={dummy2} />
            ... 
        </div>
    );
}
Danf
  • 1,409
  • 2
  • 21
  • 39
  • If the problem is with `props.cols` you should show us how you pass this property. – Dennis Vash Mar 30 '20 at 17:07
  • Yes, sorry. I added the code to the question. – Danf Mar 30 '20 at 17:17
  • I'm testing your code and it works for me?. I added data and columns as a `props` and added the missing `onRowUpdate, onRowDelete` and it seem to work fine. Here is the [codesandbox](https://codesandbox.io/s/materialtableui-l0z0g) – awran5 Mar 30 '20 at 20:30
  • Thanks @awran5. I tested the sandbox code, and if you press the edit button, then change a value and accept, the field will go back to it's original value. However, if you declare a `const comoscol = [...]` and use that instead of the props, it will work properly. I don't understand why – Danf Mar 30 '20 at 21:30
  • @Danf Yeah, I see the problem. Please check out my answer below. – awran5 Mar 31 '20 at 18:41

1 Answers1

1

I'm not quite sure about what causing this issue but it seems that MaterialTable component doesn't trigger a re-render when columns data passed as a porps.

Here is how I fixed it:

First Approach:

Create a new state for columns and trigger re-render by updating the columns via useEffect:

const [gridData, setGridData] = useState(props.data);
const [columns, setcolumns] = useState(props.col);

useEffect(() => {
  gridData.resolve();

  // update columns from props
  setcolumns(props.col);
}, [gridData, props.col]);

...

const onRowUpdate = (newData, oldData) =>
  new Promise((resolve, reject) => {
    // Reset the columns will trigger re-render as the state has changed 
    // then it will update by useEffect
    setcolumns([]);
    const { data } = gridData;
    const updatedAt = new Date();
    const index = data.indexOf(oldData);
    data[index] = newData;
    setGridData({ ...gridData, data, resolve, updatedAt });
});

codeSandbox Example.


Second Approach:

Merge data, columns into a state of object and make a copy of props data then use that copy. (I've changed the date structure a bit for testing)

// Parent
const data = [
  { param: "Admin", val: "0.03" },
  { param: "Margin", val: "0.4" },
  { param: "Price", val: "5080" }
];
const comonscol = [
  { title: "Parameters", field: "param" },
  { title: "Value", field: "val" }
];

... 

<Table col={comonscol} data={data} />


// Table.js
const [gridData, setGridData] = useState({
  data: props.data,
  columns: props.col,
  resolve: () => {},
  updatedAt: new Date()
});

const onRowUpdate = (newData, oldData) =>
  new Promise((resolve, reject) => {
    // Copy current state data to a new array
    const data = [...gridData.data];
    // Get edited row index
    const index = data.indexOf(oldData);
    // replace old row
    data[index] = newData;
    // update state with the new array
    const updatedAt = new Date();
    setGridData({ ...gridData, data, updatedAt, resolve });
});

codeSandbox Example.


Note: onRowUpdate here as an example, same goes for onRowAdd, onRowDelete

awran5
  • 4,333
  • 2
  • 15
  • 32
  • Thanks! both approaches worked. I'm going with the second one. However, I did try that before and didn't work... The only difference was this line: `let data = [...gridData.data];`, wich in my code was `const { data } = gridData;`. What is the difference? if it's not too much to ask! – Danf Mar 31 '20 at 20:19
  • 1
    Sure, you're welcome. `let data = [...gridData.data]` is the [es6](https://stackoverflow.com/questions/7486085/copy-array-by-value/53982795#53982795) way to make a copy of `gridData.data` into a new array by destructuring `gridData.data` into array `[...]` and assign the value to `data` but `{ data } = gridData` is just destructuring (taking out) `data` out of `gridData.data` – awran5 Mar 31 '20 at 21:09