0

I have these choices where user can choose Cup, Mug, and Case. The problem here is that if I'll choose Cup, the value shows "Mug". And if I'll choose "Mug", it will display "Case". What is wrong with the codes here?

import React, { useState } from "react";
import Box from "@mui/material/Box";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select";

export default function BasicSelect() {
  const [prod, setProd] = useState("");
  const [qty, setQty] = useState(0);
  const [color, setColor] = useState("");
  const [age, setAge] = useState("");

  const handleChange = (event) => {
    setProd(event.target.value);
    console.log(prod);
  };

  return (
    <Box sx={{ minWidth: 120 }}>
      <FormControl fullWidth>
        <InputLabel id="demo-simple-select-label">Age</InputLabel>
        <Select
          labelId="demo-simple-select-label"
          id="demo-simple-select"
          value={prod}
          label="Age"
          onChange={handleChange}
        >
          <MenuItem value="Cup">Cup</MenuItem>
          <MenuItem value="Mug">Mug</MenuItem>
          <MenuItem value="Case">Case</MenuItem>
        </Select>
      </FormControl>
    </Box>
  );
}

codesandbox link: https://codesandbox.io/s/basicselect-material-demo-forked-4g34r?file=/demo.js:0-1094

JS3
  • 1,623
  • 3
  • 23
  • 52
  • 1
    Your code looks fine. The value in your console.log is async so it display the it before the update See : https://stackoverflow.com/questions/54069253/usestate-set-method-not-reflecting-change-immediately – Clément Feb 02 '22 at 13:27
  • @Clément Thank you. It does work now – JS3 Feb 02 '22 at 13:48

2 Answers2

1

If you take a look at your hanldeChange

  const handleChange = (event) => {
    setProd(event.target.value);
    console.log(prod);
  };

In consol.log(prod) prod is the variable what event.target.value will be. What you are actually seeing in the output console is the previous state, because setProd hasn't re-rendered the UI in time for the prod to hold the event.target.value that you have selected in the UI.

If you want to see in the output console what value you have selected in the dropdown use console.log like this console.log(event.target.value)

You can take a look here for more information on the lifecycle of setState https://reactjs.org/docs/state-and-lifecycle.html

dparr
  • 81
  • 3
1

There is nothing wrong with you code, you're just using console.log in the wrong spot.

When listeners such as onChange's function are invoked, their invocation is somehow controlled by React. Every DOM event in React is wrapped in a custom function which makes possible, among other things, using setState inside the listener.

In your case, when setProd is invoked, a sync task will be scheduled. This task's purpose is to synchronize the Fiber tree, which involves finding out what's changed and committing the new changes so that the results can be seen in the browser.
This sync task I mentioned earlier will start executing after the listener has finished executing - so, logging to the console after calling setProd will use the old value, not the one that has just been provided to setProd.

In order to set that, you can add another console.log that will print the value that you'd probably expect:

console.warn("REAL VALUE: ", prod);

const handleChange = (event) => {
  setProd(event.target.value);
  console.log(prod);
};

To spice things up a little, you can add a debugger; keyword in the listener so that you can get a better understanding of what's going on there:

enter image description here

In the image above, the breakpoint is stopped at console.log(prod). You can see in the bottom right corner the syncQueue which contains the task which is responsible for updating the fiber tree. It has been added there as a result of using setProd.

Also notice that after the listener finishes, flushSyncCallbackQueue will be invoked(it's in the finally block) and this will lead to executing tasks which are in syncQueue.

Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31