Error while inline editing in material-table + reactjs - reactjs

I am using Material-Table along-with ReactJS to create data-table. The table properly loads and displays the data properly but once I edit the data, I get the below error in developer console
Below is my code for Material Table
<MaterialTable
title={tbl_title}
columns={datatable.columns}
data={datatable.data}
options={tableOptions}
editable={{
onRowAdd: newData =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const data = datatable.data;
data.push(newData);
setTable({ data }, () => resolve());
}
resolve()
}, 1000);
}),
onRowUpdate: (newData, oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const data = datatable.data;
const index = data.indexOf(oldData);
data[index] = newData;
setTable({ data }, () => resolve());
}
resolve()
}, 1000)
}),
}}
/>
I am just using the same code sample from the provider with small modification to meet React Functional Component pattern. It would be great if anyone can point me in the right direction.
An older issue for similar error Github Issue: 400 is listed but has no guidance for the issue.
[EDIT]
I am not sure if this helps, but I am seeing a very unusual thing with last modified date for all files after a fresh install of the dependency.
Would this affect somehow the functioning of the dependency package?
Further, error details while editing/adding row
Code changes I used to fix the error in above image
File: node_modules/material-table/dist/components/m-table-body.js:113
return renderData.map(function (data, index) {
if (typeof(data.tableData) != "undefined") {
if (data.tableData.hasOwnProperty("editing")) {
return React.createElement(_this.props.components.EditRow, {
columns: _this.props.columns.filter(function (columnDef) {
return !columnDef.hidden;
}),
components: _this.props.components,
data: data,
icons: _this.props.icons,
localization: (0, _objectSpread2["default"])({}, MTableBody.defaultProps.localization.editRow, _this.props.localization.editRow, {
dateTimePickerLocalization: _this.props.localization.dateTimePickerLocalization
}),
key: index,
mode: data.tableData.editing,
options: _this.props.options,
isTreeData: _this.props.isTreeData,
detailPanel: _this.props.detailPanel,
onEditingCanceled: _this.props.onEditingCanceled,
onEditingApproved: _this.props.onEditingApproved,
getFieldValue: _this.props.getFieldValue
});
} else {
return React.createElement(_this.props.components.Row, {
components: _this.props.components,
icons: _this.props.icons,
data: data,
index: index,
key: "row-" + data.tableData.id,
level: 0,
options: _this.props.options,
localization: (0, _objectSpread2["default"])({}, MTableBody.defaultProps.localization.editRow, _this.props.localization.editRow),
onRowSelected: _this.props.onRowSelected,
actions: _this.props.actions,
columns: _this.props.columns,
getFieldValue: _this.props.getFieldValue,
detailPanel: _this.props.detailPanel,
path: [index + _this.props.pageSize * _this.props.currentPage],
onToggleDetailPanel: _this.props.onToggleDetailPanel,
onRowClick: _this.props.onRowClick,
isTreeData: _this.props.isTreeData,
onTreeExpandChanged: _this.props.onTreeExpandChanged,
onEditingCanceled: _this.props.onEditingCanceled,
onEditingApproved: _this.props.onEditingApproved,
hasAnyEditingRow: _this.props.hasAnyEditingRow,
treeDataMaxLevel: _this.props.treeDataMaxLevel
});
}
}
});

Related

Create and update inside map function

I'm trying to find the right way to create and consequently update inside a map function.
These are the steps I need:
Map function "reads" the array of elements ids
Create new record on "leads_status" table
Using the new record id (from "leads_status") "leads" table is updated using "leads_status.id" as foreign key related to "leads.id_ls"
This is the code I tried.
const [create, { isLoading: isLoadingCreate, error: errorCreate }] = useCreate();
const [record, setRecord] = React.useState(null);
leadsIDS.map((value, index) => {
create('leads_status', {
data: {
id_lead: value,
id_status: 5
}
}, {
onSuccess: ({ id }) => {
setRecord([id, value]);
},
onError: () => {
console.log();
}
});
update('leads', {
id: record[1],
data: {
id_ls: record[0]
}
}, {
enabled: !isLoadingCreate && record !== null
}, {
onSuccess: () => {
console.log(record);
},
onError: error => notify('Error', { type: 'warning' })
})
})
I tried also to put the "update" function inside the "create --> onSuccess" but also there the code is not working as I want.
In "leads_status" table records are always created for each element in "leadsIDS" array but in "leads" table only 1 records is updating.
Where am I wrong?
The useCreate and useUpdate hooks are designed for single actions. If you want to chain several actions, I suggest you use the useDataProvider hook, instead, which lets you manipulate Promises.
const dataProvider = useDataProvider();
const notify = useNotify();
try {
await Promise.all(leadsIDS.map(async (value, index) => {
const { data: leadStatus } = await dataProvider.create('leads_status', {
data: {
id_lead: value,
id_status: 5
}
});
await dataProvider.update('leads', {
id: value,
data: { id_ls: leadStatus.id }
});
}));
} catch (e) {
notify('Error', { type: 'warning' });
}

How can i auto refresh or render updated table data form database in material UI table after doing any operation in React?

Here useVideos() give us all videos form database. After adding a new video the new entry is not append in the Material UI table , but if I refresh the page then it's showing that new entry. Now I want to show this new entry after add operation. Please help me to do this! Thanks in Advance.
const initialState = [{}];
const reducer = (state, action) => {
switch (action.type) {
case "videos":
const data = [];
let cnt = 1;
action.value.forEach((video, index) => {
data.push({
sl: cnt,
noq: video.noq,
id: index,
youtubeID: video.youtubeID,
title: video.title,
});
cnt = cnt + 1;
});
return data;
default:
return state;
}
};
export default function ManageVideos() {
const { videos, addVideo, updateVideo, deleteVideo } = useVideos("");
const [videoList, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({
type: "videos",
value: videos,
});
}, [videos]);
const columns = [
{ title: "Serial", field: "sl" },
{ title: "Title", field: "title" },
{ title: "Number of Qusetion", field: "noq" },
{ title: "Youtube Video Id", field: "youtubeID" },
];
return (
<div>
<MaterialTable
title="Videos Table"
data={videoList}
columns={columns}
editable={{
onRowAdd: (newData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
addVideo(newData);
resolve();
}, 1000);
}),
onRowUpdate: (newData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
updateVideo(newData);
resolve();
}, 1000);
}),
}}
/>
</div>
);
}
Since the information provided is a bit lacking, I'll assume that the useEffect hook is not working when you update your videos (check it with consle.log("I'm not working") and if it does work then you can just ignore this answer).
You can define a simple state in this component, let's call it reRender and set the value to 0, whenever the user clicks on the button to add a video you should call a function which adds 1 to the value of reRender (()=>setReRender(prevState=>prevState+1)). In your useEffect hook , for the second argument pass reRender. This way, when the user clicks to submit the changes , reRender causes useEffect to run and dispatch to get the new data.
If this doesn't work , I have a solution which takes a bit more work. You will need to use a state manager like redux or context api to store your state at a global level. You should store your videos there and use 1 of the various ways to access the states in this component (mapStateToProps or store.subscribe() or ...). Then pass the video global state as the second argument to useEffect and voilĂ , it will definitely work.

PayPal React shows extra buttons after changing amount

WITHOUT react-paypal-button-v2 ~~~has an ovehead of 60KB
Similar question here but they suggest react-paypal-button-v2
I'm Trying to make a React PayPal button that changes the billing amount on props change.
I call the following component with props price and every time the price change i would like to re-render the button to update the actual price. WITHOUT react-paypal-button-v2
const PaypalForm = props => {
let paypalRef = useRef();
useEffect(() => {
window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: "test",
amount: {
currency_code: "USD",
value: props.price
}
}
]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
console.log(order);
},
onError: err => {
setError(err);
console.error(err);
}
})
.render(paypalRef.current);
}, [props.price]);
return (
<Row className="justify-content-center">
{error && <div>Uh oh, an error occurred! {error.message}</div>}
<div ref={paypalRef} />
</Row>
);
};
Everything is working except that a new button is created and added in the bottom of old one at each props change. I would like my new button to replace the old one. Without using react-paypal-button-v2
Something like:
useEffect(() => {
if(window.myButton) window.myButton.close();
window.myButton = window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: "test",
amount: {
currency_code: "USD",
value: props.price
}
}
]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
console.log(order);
},
onError: err => {
setError(err);
console.error(err);
}
});
window.myButton.render(paypalRef.current);
However, you do not actually need to re-render the button on price change!
You can do value: document.getElementById('...').value or similar (or whatever variable or function call you need)
In your example, if props.price returns the (new/current) desired value when the button is clicked, then that value will be used.
Basically, the createOrder function isn't called until you click a button.

Inline MaterialTable Edit with DropDown

I'm attempting to create a MaterialTable with an inline editable field that has a dropdown. The problem seems to be in the columns object. With the lookup attribute, one can specify key:value pairs as dropdown list items. My dilemma seems to be that I am not able to iterate over a list and add the key-value pairs in the dynamic fashion below. It seems to only work when written like lookup:{ 1: "Test Value", 2: "Test Value 2" }. Please explain if my understanding is incorrect.
<MaterialTable
title="Available Attributes"
icons={this.tableIcons}
data={availableAttributesList}
columns={[
{ title: 'Attribute', field: 'name' },
{
title: 'Data Type',
field: 'dataType',
lookup: { dataTypePayload.map((attribute, name) => ({
attribute.id : attribute.dataType
}))}
}
]}
options={{
actionsColumnIndex: -1
}}
editable={{
onRowAdd: newData =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const data = editableAvailableAttributesList;
data.push(newData);
this.setState({ data }, () => resolve());
}
resolve();
}, 1000);
}),
onRowUpdate: (newData, oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const data = editableAvailableAttributesList;
const index = data.indexOf(oldData);
data[index] = newData;
this.setState({ data }, () => resolve());
}
resolve();
}, 1000);
}),
onRowDelete: oldData =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
let data = availableAttributesList;
const index = data.indexOf(oldData);
data.splice(index, 1);
this.setState({ data }, () => resolve());
}
resolve();
}, 1000);
})
}}
/>
The map creates an array of objects, which you have then placed inside another object.
As you've noticed, this won't work. To get the desired format, try this:
<MaterialTable
// ...other props
columns={[
{ title: 'Attribute', field: 'name' },
{
title: 'Data Type',
field: 'dataType',
lookup: dataTypePayload.reduce((acc: any, attribute: any) => {
acc[attribute.id] = attribute.dataType
return acc
}, {})
}
]}
// ...other props
/>
Hope that helps!

React Material-Table editing from props using hooks

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

Resources