Reputation: 55
I am trying to update a state which is of type --> [{}, {},...]. When I update the data in single object it works perfectly. But when I add another row and add some data in that object then both rows will have same data object e.g.
[{"name": "John"}, {"name": "John"}]
But, I want it to be like
[{"name": "John"}, {"name": "Martha"}]
These are just examples not actual object. Object is given below.
The code I am using for that is :
const WorkTab:React.FC<{workHandler: any, workExp: any, err: any}> = ({workExp, err}) => {
const [workIndex, setWorkIndex] = useState(workExp.length === 0 ? 1 : workExp.length);
const [work, setWork] = useState(workExp)
const { userInfo } = useSelector((state: any) => state.userDetails);
const handleChange = (e: any, input: string, i: number, value?: any) => {
const [ ...tempWork ] = work;
if (work.length === 0 || tempWork[i] === undefined) {
tempWork.push(workTemplate);
}
if (input === "company") {
tempWork[i][input] = e.target.value;
} else if (input === "title") {
tempWork[i][input] = value;
} else {
tempWork[i][input] = e.toISOString();
}
setWork([...tempWork]);
};
const WorkGrid = (n: number) => {
return (
<Grid container spacing={3} style={{ marginTop: "5px" }}>
<Grid item xs={12} md={4}>
<TextField
sx={{ width: { lg: "98%", md: "100%" } }}
label="Firm Name"
variant="outlined"
onChange={(e) => handleChange(e, "company", n)}
error={err && work[n]?.company === ""}
defaultValue={userInfo?.work_experiences[n]?.company}
/>
</Grid>
<Grid item xs={12} md={5}>
<Autocomplete
sx={{ width: "100%" }}
disableClearable
options={preferences.jobFunction}
defaultValue={userInfo?.work_experiences[n]?.title}
onChange={(e, value) => handleChange(e, "title", n, value)}
renderInput={(params) => (
<TextField {...params} label="Job Function" />
)}
/>
</Grid>
<Grid
item
xs={12}
md={3}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<MobileDatePicker
label="Start Date"
views={["year", "month"]}
inputFormat="MM/yyyy"
value={new Date(work[n]?.start_date)}
onChange={(e, newValue) => handleChange(e, "start_date", n, newValue)}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
</Grid>
</Grid>
)};
return (
<div>
{[...Array(workIndex)].map((n: number, index: number) => (
<div key={index}>{WorkGrid(index)}</div>
))}
<div style={{display: "flex", justifyContent: "space-between"}} className='state-btns'>
<Button
sx={{ color: "#000", textTransform: "capitalize" }}
onClick={() => setWorkIndex((prev: number) => prev + 1)}
>
Add other work experience +
</Button>
{workIndex !== 1 && <Button onClick={() => setWorkIndex((prev: number) => prev - 1)}><Delete style={{color: "black"}} /></Button>}
</div>
</div>
);
};
Also the workTemplate is:
const workTemplate = {
title: "",
company: "",
industry: "",
position: "",
city: "",
start_date: "",
}
I am not able to figure out why it is behaving like this.
Can anyone please explain this behavior?
Upvotes: 2
Views: 302
Reputation: 203512
You have an issue of state mutation. Every element you push into the work
state is a reference to the workTemplate
object. The handleChange
handler then is mutating the state object references.
const handleChange = (e: any, input: string, i: number, value?: any) => {
const [ ...tempWork ] = work;
if (work.length === 0 || tempWork[i] === undefined) {
tempWork.push(workTemplate); // <-- reference to `workTemplate` object
}
if (input === "company") {
tempWork[i][input] = e.target.value; // <-- mutate reference
} else if (input === "title") {
tempWork[i][input] = value; // <-- mutate reference
} else {
tempWork[i][input] = e.toISOString(); // <-- mutate reference
}
setWork([...tempWork]);
};
Use a functional state update to update from the previous state. Create a shallow copy of all state/objects being updated. You will find it helpful/useful to also copy any template objects as a way to keep them from being accidentally mutated.
Example:
const handleChange = (e: any, input: string, i: number, value?: any) => {
const tempWork = work.slice(); // <-- shallow copy
if (!tempWork.length || !tempWork[i]) {
tempWork.push({ ...workTemplate }); // <-- shallow copy template
}
let newValue = e.toISOString();
if (input === "company") {
newValue = e.target.value;
} else if (input === "title") {
newValue = value;
}
const newWork = tempWork.map((item, index) => index === i
? {
...item, // <-- shallow copy element
[input]: newValue // <-- update property
}
: item
);
setWork(newWork);
};
Upvotes: 1