0

I am working with a variable in useState and when I try to update a property of this object the change is not set.

This is the function:

const addItemVoucher = () => {
  const totalItem = valueItem.quantity * valueItem.price;
  setValueItem((state) => ({ ...state, total: totalItem }));
  console.log(valueItem);
  const itemTemp = Object.assign({}, valueItem);
  setItemVouchers((itemVouchers) => [...itemVouchers, itemTemp]);
  setValueItem({
    id: "",
    article: "",
    price: 0.0,
    quantity: 0.0,
    total: 0.0
  });
};

I assign the value of "totalitem" to the "total" property of the variable "valueItem", but it does not work, so when I print on the screen the value of total, in a datagrid, is 0

code:

import React, { useState, Fragment } from "react";
import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField";
import Container from "@mui/material/Container";
import Box from "@mui/material/Box";
import { DataGrid } from "@mui/x-data-grid";
import { Button, gridClasses } from "@mui/material";

const TmpItem = () => {
   const [pageSize, setPageSize] = useState(15);
   const [valueItem, setValueItem] = useState({
     id: "",
     article: "",
     price: 0.0,
     quantity: 0.0,
     total: 0.0
   });
   const [itemVouchers, setItemVouchers] = useState([]);

   const columnsItem = [
    {
  field: "index",
  width: 70,
  renderHeader: () => <strong>{"NRO"}</strong>,
  headerAlign: "center",
  renderCell: (index) => index.api.getRowIndex(index.row.id) + 1
},
{
  field: "article",
  width: 300,
  renderHeader: () => <strong>{"NAME"}</strong>,
  headerAlign: "center"
},
{
  field: "quantity",
  width: 100,
  renderHeader: () => <strong>{"QUANTITY"}</strong>,
  headerAlign: "center"
},
{
  field: "price",
  width: 100,
  renderHeader: () => <strong>{"PRICE"}</strong>,
  type: "number",
  cellClassName: "font-tabular-nums",
  headerAlign: "center"
},
{
  field: "total",
  width: 100,
  renderHeader: () => <strong>{"TOTAL"}</strong>,
  type: "number",
  cellClassName: "font-tabular-nums",
  headerAlign: "center"
}
];

const handleChangeValueItem = (event) => {
  setValueItem({ ...valueItem, [event.target.name]: event.target.value });
};

const addItemVoucher = () => {
  const totalItem = valueItem.quantity * valueItem.price;
  setValueItem((state) => ({ ...state, total: totalItem }));
  console.log(valueItem);
  const itemTemp = Object.assign({}, valueItem);
  setItemVouchers((itemVouchers) => [...itemVouchers, itemTemp]);
  setValueItem({
    id: "",
    article: "",
    price: 0.0,
    quantity: 0.0,
    total: 0.0
  });
 };

 return (
  <Fragment>
  <Container maxWidth="md">
    <Box sx={{ flexGrow: 1 }}>
      <form id="new-form-voucher">
        <Grid
          container
          spacing={{ xs: 1, md: 2 }}
          columns={{ xs: 4, sm: 8, md: 12 }}
        >
          <Grid item xs={4} sm={4} md={6}>
            <TextField
              label="ID"
              name="id"
              value={valueItem.id}
              variant="outlined"
              fullWidth
              onChange={handleChangeValueItem}
              size="small"
            />
          </Grid>
          <Grid item xs={4} sm={4} md={6}>
            <TextField
              label="Article"
              name="article"
              value={valueItem.article}
              variant="outlined"
              fullWidth
              onChange={handleChangeValueItem}
              size="small"
            />
          </Grid>
          <Grid item xs={4} sm={4} md={6}>
            <TextField
              label="Price"
              name="price"
              value={valueItem.price}
              type="number"
              variant="outlined"
              fullWidth
              onChange={handleChangeValueItem}
              size="small"
            />
          </Grid>
          <Grid item xs={4} sm={4} md={6}>
            <TextField
              label="Quantity"
              name="quantity"
              value={valueItem.quantity}
              type="number"
              variant="outlined"
              fullWidth
              onChange={handleChangeValueItem}
              size="small"
            />
          </Grid>
          <Grid item xs={4} sm={4} md={6}>
            <Button fullWidth onClick={addItemVoucher} variant="contained">
              Add
            </Button>
          </Grid>
          <Grid item xs={4} sm={8} md={12} textAlign="center">
            <div style={{ width: "100%" }}>
              <DataGrid
                autoHeight
                disableExtendRowFullWidth
                disableColumnFilter
                disableSelectionOnClick
                showCellRightBorder
                showColumnRightBorder
                rows={itemVouchers}
                columns={columnsItem}
                pageSize={pageSize}
                onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
                rowsPerPageOptions={[15, 20, 30]}
                getRowHeight={() => "auto"}
                sx={{
                  [`& .${gridClasses.cell}`]: { py: 1 }
                }}
                pagination
              />
            </div>
          </Grid>
        </Grid>
      </form>
    </Box>
  </Container>
  </Fragment>
  );
 };

 export default TmpItem;

code in sandbox : https://codesandbox.io/s/reverent-tree-rnc31d?file=/TmpItem.js:0-5075

Thanks for all.

Ronald
  • 291
  • 3
  • 7
  • 23

2 Answers2

1

Let me explain the problem in the above code by adding comments:

const addItemVoucher = () => {
  const totalItem = valueItem.quantity * valueItem.price;
  // this will tell React to chain an update event on the "valueItem"
  // but this will happen on the next re-render of your component
  setValueItem((state) => ({ ...state, total: totalItem }));
  // so this will still log the current valueItem with the old "total"
  console.log(valueItem);
  // now you are creating here an outdated item copy
  const itemTemp = Object.assign({}, valueItem);
  // you add this outdated copy to your list, but for next re-render
  setItemVouchers((itemVouchers) => [...itemVouchers, itemTemp]);
  // you update the "valueItem" again, but again for next re-render
  // since React@18 this means you only see this update and NOT the
  // previous update you did before
  setValueItem({
    id: "",
    article: "",
    price: 0.0,
    quantity: 0.0,
    total: 0.0
  });
 };

To fix this, you might want to do this instead:

const addItemVoucher = () => {
  const totalItem = valueItem.quantity * valueItem.price;
  setItemVouchers((itemVouchers) => [...itemVouchers, { ...valueItem, total: totalItem }]);
  setValueItem({
    id: "",
    article: "",
    price: 0.0,
    quantity: 0.0,
    total: 0.0
  });
 };
Viktor Luft
  • 857
  • 3
  • 8
  • the value of totalItem is fine, but the value in total property is zero – Ronald Sep 13 '22 at 05:49
  • The `total` property not getting set is because React state updates aren't immediate. You can't log or use the state you just enqueued an update for and expect to see the value it *will* be on some subsequent render cycle. I suspect the quantity and price are number-like strings and get implicitly converted to number types. You can try something like `"13.5" * "2"` out in the console to see for yourself the result is `27` and not any string value. – Drew Reese Sep 13 '22 at 05:50
  • @DrewReese You are totally right. My mistake. Let me fix the answer. – Viktor Luft Sep 13 '22 at 06:18
  • 1
    There's not much reason to edit your answer as this question has been marked as a duplicate. This and similar questions are asked on a near-daily basis. – Drew Reese Sep 13 '22 at 06:20
  • 1
    New as contributor. Thanks for the hint. Did it anyway :) – Viktor Luft Sep 13 '22 at 06:25
-2

Try this way

const addItemVoucher = () => {
  const totalItem = valueItem.quantity * valueItem.price;
  const tempValueItem = {...valueItem}
  tempValueItem['total'] = totalItem
  const itemTemp = Object.assign({}, valueItem);
  setItemVouchers((itemVouchers) => [...itemVouchers, itemTemp]);
  tempValueItem['id'] = ''
  tempValueItem['article'] = ''
  tempValueItem['price'] = 0.0
  tempValueItem['id'] = 0.0
  tempValueItem['total'] = 0.0
  setValueItem(tempValueItem);
};
Shah Vipul
  • 625
  • 7
  • 11
  • Here you are directly mutating React internal state. Please don't do that. – Viktor Luft Sep 13 '22 at 05:34
  • nope, here i am taking all things in a temp var that isn't directly affecting React internal state. Issue with above one is that when we try to set state two time in one function it will always discard first one. – Shah Vipul Sep 13 '22 at 05:38
  • 1
    What exactly do you think `const tempValueItem = valueItem` is saving a reference to? Also, React doesn't discard enqueued state updates, they are all certainly processed, subsequent updates can *possibly* overwrite previous updates when they are batch processed. – Drew Reese Sep 13 '22 at 05:41
  • const tempValueItem = {...valueItem} edited – Shah Vipul Sep 14 '22 at 05:19