3

React unexpectedly keeps an old value of a variable produced by React.useState(...). I press the button [1] and then [2]. In 3000 ms I expect to see "null" in the alerting message, but still see "42". Wrapping handleDisplay into React.useCallback with dependency on [val] does not help.

function App() {
    const [val, setVal] = React.useState(42)

    const handleHide = () => {
        setVal(null)
    }

    const handleDisplay = () => {
        setTimeout(() => {
            alert(val)
        }, 3000)
    }

    return (
        <div>
            <p>VAL: {val ? val : 'null'}</p>
            <button onClick={handleDisplay}>[1] display value with delay</button>
            <button onClick={handleHide}>[2] hide immediately</button>
        </div>
    )
}

At the same time — value on the page (rendered via VAL: { val ? val : 'null' }) is correct, it is "null" as expected. Any ideas about what do I do wrong, and how can I get "null" in my alert(...)?

P.S. Here is a sandbox with a live example https://codesandbox.io/s/react-usestate-bug-24ijj

Maxim Zaytsev
  • 43
  • 1
  • 5
  • i hav'nt looked this up but it seems to me like the alert and its data to"alert" are both sent to the DOM immediately and starts a countdown in order to show that VAL... unlike reactjs, the alert() doesnt seem to allow values to be changed in the DOM. You should use a Modal instead. That way you can change values at any time. – Keaton Benning Apr 07 '21 at 18:16

1 Answers1

2

At the first render, val is just a constant number with the value 42 that can never change. This is the value passed to setTimeout when you click button 1. Clicking button 2 before 3 seconds have passed will cause a new render in which a new instance of val has the value null, but this will not affect the value bound in the first render. You probably want to read up on the useEffect hook. Dan Abramov has a great, but long article here, that mentions just this issue: https://overreacted.io/a-complete-guide-to-useeffect/

Jonas Høgh
  • 10,358
  • 1
  • 26
  • 46
  • thank you again for your answer, I have read the whole article you shared — and finally solved my problem using useEffect. But I did that not in the way I wanted initially — from the article I've got how to get current rendered value inside the useEffect — but still have no idea how to get it in the async callback (in my case — setTimeout in handleDisplay). Or is it possible at all, since Dan writes that "Each Render Has Its Own… Everything" ?.. I'd be really grateful if you share any clues on that. – Maxim Zaytsev Apr 08 '21 at 15:24
  • You're welcome. There's an example in the article using useRef to get the modified version of the state in a setTimeout callback:https://codesandbox.io/embed/rm7z22qnlp?codemirror=1 – Jonas Høgh Apr 08 '21 at 17:20