2

There is an input element in my code:

<input id="input" className="ml-2" type="number" name="Qty" min="1" max="999" value={qty} onChange={(e) => {changeQty(e)}}/>

And the onChange handler is:

    const changeQty = (e) => {
        setQty(e.target.value);
        console.log(qty);
    }

So I suppose that whatever I enter in the input field will be printed out in the console, but the fact is there is delay.

For example, the default value is 1, then I type 2, then 1 will be printed out. Then I type 3, then 2 will be printed out. The number printed out is the previous one I entered instead of the current one, as shown below:

enter image description here

Could anyone tell me how to remove the delay?

I read useState set method not reflecting change immediately, but still cannot solve it.

I tried the code below as the answer indicates,

useEffect(() => {
    setQty(document.getElementById("input").value)
}, [qty]);

but an error ReferenceError: Cannot access 'qty' before initialization occurred.

Thanks!

powerseed
  • 1,090
  • 3
  • 17
  • 29
  • Without the whole code it's impossible to answer this question. – Fez Vrasta May 29 '20 at 16:13
  • Where are you calling setState? A console.log outside the setState context will print the old value. – aksappy May 29 '20 at 16:17
  • 1
    This boils down to the classic: "React setState is async" you have to use a callback or an effect to wait until the new state is set before using it. – DBS May 29 '20 at 16:23

4 Answers4

4

It's hard to say from the current question, but I'm going to assume that you're writing a function component and are using a useState hook. That is, somewhere above the changeQty you have something like:

const [qty, setQty] = useState(0);

If that's the case, then the answer is directly in the assignment of qty above. qty is set when you call the useState hook and will not change for the duration of the execution.

setQty does not update the value of qty. It updates the internal state, which will trigger a re-render. That means the next time your render function is invoked, qty will have the value you used.

But for the rest of the execution of the function, qty will remain whatever value it was when useState was called.

If you need the new value of qty in the same iteration of your function, you can capture that as a new variable.

const [qty, setQty] = useState(0);
let updatedQty = qty;

const changeQty = (e) => {
  updatedQty = e.target.value;
  setQty(updatedQty);
  console.log(updatedQty);
}
Noah Callaway
  • 617
  • 5
  • 16
1

this.setState() has two forms that could help you here:

Functional Form

this.setState((prevState, props) => ({}))

Callback Form

this.setState(nextState, () => {
    // instructions for immediately-after the state update
})

It sounds like you could use the callback form here to defer some calculations into the callback.

Also, you can chain those if it's a warzone out there:

this.setState(nextState, () => {
    this.setState(nextState, () => {
        this.setState(nextState, () => {
            // you can do this, but probably don't
        })
    })
})

But, notice the extreme indenting that is occurring; it's reminiscent of callback hell and Promise christmas-tree hell.

agm1984
  • 15,500
  • 6
  • 89
  • 113
0

The problem is that setQty is asynchronous. Try this:

useEffect(() => {
console.log(qty);

}, [qty]);

You will normally get current value in this console.log. In your code, your console.log may execute before the state is updated because setQty not synchronous as you may think

Ibra
  • 338
  • 4
  • 9
  • Thank you, your answer also works for my question, but I am not only printing it out on console, I need the updated value to toggle the Add to cart button, but when I do document.getElementById("AddToCart").classList.add("disabled"); in the useEffect function, the element cannot be found. Thus, I have to accept another question which is simpler and more universal. Thanks you all the same! – powerseed May 29 '20 at 16:52
0

Since you didn't share all of your code, it's difficult to answer your question exactly, but it looks like you're using React's hooks, so I've created an example that uses hooks correctly to set the qty state and log the current qty value.

const Quantity = function() {
  const [qty, setQty] = React.useState(0);
  const changeQty = (e) => {
    setQty(e.target.value);
  };
  React.useEffect(() => {
    console.log(qty);
  }, [qty]);
  
  return (
      <input id="input" className="ml-2" type="number" name="Qty" min="1" max="999" value={qty} onChange={(e) => {changeQty(e)}}/>
  );
};
ReactDOM.render(<Quantity />, document.getElementById("app_root"));
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser-polyfill.min.js"></script>
</head>

<body>
  <div id="app_root"></div>
</body>
Steven Oxley
  • 6,563
  • 6
  • 43
  • 55
  • Thank you, your answer also works for my question, but I am not only printing it out on console, I need the updated value to toggle the `Add to cart` button, but when I do `document.getElementById("AddToCart").classList.add("disabled");` in the `useEffect` function, the element cannot be found. Thus, I have to accept another question which is simpler and more universal. Thanks you all the same! – powerseed May 29 '20 at 16:51
  • @powerseed I'm glad you found an answer that worked for you. I think you should consider using some shared state between your `Quantity` component and your `AddToCart` component. In React code, using `document.getElementById` is an indication that you're circumventing the advantages that React provides. – Steven Oxley May 29 '20 at 21:03