I am trying to dynamically add and remove text fields for user entry. When I click the button to remove a particular row, I want to modify the state object to remove the data of that row thereby causing the row to disappear. However I am unable to do that and I am getting an error in the render loop as the compiler is unable to find the value for the row. The error is as follows
Cannot read properties of undefined (reading 'from')
I want it to look at the new state object and display the number of rows accordingly. Here is the code for sandbox
import "./styles.css";
import React from "react";
import { Button, Grid, Paper } from "@mui/material";
import { TextField, Icon } from "@mui/material";
interface State {
serialInputObjects: any;
}
class SerialQRScanClass extends React.PureComponent<State> {
state = {
serialInputObjects: {
0: { from: "", to: "", except: "" }
}
};
//Delete the already registered scanned codes code here
handleAdd = () => {
const objectLength = Object.keys(this.state.serialInputObjects).length;
console.log(objectLength);
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[objectLength]: {
from: "",
to: "",
except: "",
fromError: "",
toError: ""
}
}
}));
console.log(this.state.serialInputObjects);
};
handleChangeFromSerials = (key: any, data: string) => {
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[key]: { ...prevState.serialInputObjects[key], from: data }
}
}));
console.log(this.state.serialInputObjects);
//this.calculation(key);
};
handleChangeToSerials = (key: any, data: string) => {
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[key]: { ...prevState.serialInputObjects[key], to: data }
}
}));
console.log(this.state.serialInputObjects);
//this.calculation(key);
};
handleRemove = (key) => {
console.log(this.state.serialInputObjects);
this.setState((prevState) => ({
...prevState,
serialInputObjects: { ...prevState.serialInputObjects, [key]: undefined }
}));
console.log(this.state.serialInputObjects);
};
render() {
return (
<Paper elevation={3} className="abc">
<Button onClick={this.handleAdd}>ADD NEW FIELD</Button>
{Object.keys(this.state.serialInputObjects).map((key) => (
<div key={key}>
<Grid container alignItems="flex-end">
<Grid item className="bcd">
<TextField
fullWidth
label={"FROM"}
placeholder={"Ex.100"}
value={this.state.serialInputObjects[key]["from"]}
onChange={(e) =>
this.handleChangeFromSerials(key, e.target.value)
}
error={
Boolean(this.state.serialInputObjects[key]["fromError"]) ||
false
}
helperText={this.state.serialInputObjects[key]["fromError"]}
margin="none"
size="small"
/>
</Grid>
<Grid item className="bcd">
<TextField
fullWidth
label={"To"}
placeholder={"Ex.100"}
value={this.state.serialInputObjects[key]["to"]}
onChange={(e) =>
this.handleChangeToSerials(key, e.target.value)
}
error={
Boolean(this.state.serialInputObjects[key]["toError"]) ||
false
}
helperText={this.state.serialInputObjects[key]["toError"]}
margin="none"
size="small"
/>
</Grid>
<Grid
item
className={"abc"}
style={{ paddingLeft: "10px" }}
></Grid>
<div style={{ display: key === "0" ? "none" : "block" }}>
<Button onClick={(e) => this.handleRemove(key)}>
<Icon fontSize="small">remove_circle</Icon>
</Button>
</div>
</Grid>
</div>
))}
</Paper>
);
}
}
export default function App() {
return (
<div className="App">
<SerialQRScanClass />
</div>
);
}
I want to modify the state object to remove the data of that row thereby causing the row to disappear.
If you want to cause the row to disappear you have to update the serialInputObjects
object without the row you want to delete.
Right now you are just assigning the value undefined
to the selected row so it still exists but it doesn't contain the property from
anymore, and because you are referring to that property here:
value={this.state.serialInputObjects[key]["from"]}
Javascript tells you that it is undefined
, beacuse it doesn't exists.
Now for this you will need destructuring assigment, this is the solution:
handleRemoveKey(key){
const { [key]: renamedKey, ...remainingRows } = this.state.serialInputObjects;
this.setState((prevState) => ({
...prevState,
serialInputObjects: { ...remainingRows }
}));
}
However if you want to know why that first line of the function works, follow me.
Let's say you have this object:
const myObj = {
a: '1',
b: '2',
c: '3',
}
And let's say we need to separate the c
prop from the others, we do that like this:
const { c, ...otherProps } = myObj
This would result in the creation of 2 new const, the const c
and the const otherProps
:
c
will contain the value '3'
otherProps
will contain this object { a: '1', b: '2' }
But what happen if there is already a variable named c
? Our newly created c
const in the destructuring statement would be a duplicate which is not allowed of course, so what can we do? We rename our newly created c
const while we are destructuring myObj
, like this:
const { c: renamedC, ...otherProps } = obj
This way the newly created const would be renamedC
and therefore there will be no conflict with the other c
we just supposed for the example.
That is exactly we are doing here:
handleRemoveKey(key){ // <------ here is already a "variable" named key
const { [key]: renamedKey, ...remainingRows } = this.state.serialInputObjects;
// so we had to rename our newly created 'key' const to 'renamedKey' to avoid conflicts.
As a side note I would suggest serialInputObjects
should be an array(of objects) and not an object because arrays are ordered while objects are not, this whole process would have been easier if serialInputObjects
would have be an array.