Search code examples
reactjsuse-state

React SetState not working till second render if HTML element updated inside a function


I am working on a useState example, and I am finding that it doesn't work until the second render. The first time around it holds the default value of 0.

import React, { useState } from "react";

const MyState = () => {
  const [age, setage] = useState(0);

  function handleClick() {
    var enteredAge = document.getElementById("txtAge").value;
    if (enteredAge) {
      setage(enteredAge);
      if (enteredAge > 50) {
        document.getElementById("lblResult").innerText = "Wow " + age + "! You're old, rest up!";
      } else {
        document.getElementById("lblResult").innerText = "Wow " + age + "! Young gun!";
      }
    }
  }

  return (
    <div>
      Enter Your Age:
      <input type="text" id="txtAge" name="age" />
      <button onClick={handleClick}> Check </button>
      <label id="lblResult"></label>
    </div>
  );
};

export default MyState;

If I do something like the following, this works fine on every render. But if I use setState inside the function like I showed above, then the age inside the innerText still holds old value. Why is that, and what the right way to do this?

<label id="lblResult">{age}</label>

Solution

  • You should not mutate the DOM node directly, since react maintains the V-DOM and mutations you are doing to lblresult will be lost after re-render. The right way is to use the state value and conditionally render the data.

    import React, { useState } from 'react';
    
    const MyState = () => {
      const [age, setage] = useState(0);
    
      function handleClick() {
        var enteredAge = document.getElementById('txtAge').value;
        if (enteredAge) {
          setage(enteredAge);
        }
      }
    
      return (
        <div>
          Enter Your Age:
          <input type="text" id="txtAge" name="age" />
          <button onClick={handleClick}> Check </button>
          <label id="lblResult">
            {age > 50
              ? `"Wow ${age}! You're old, rest up!`
              : `Wow ${age}! Young gun!`}
          </label>
        </div>
      );
    };
    
    export default MyState;
    

    Update:

    As Bjorn mentioned, You could use refs instead of getElementById to get the input field value. Ref is a Object with persistent reference between re-render, the current property of the ref holds the DOM node.

    import React, { useState, useRef } from 'react';
    
    const MyState = () => {
      const [age, setage] = useState(0);
      const inputRef = useRef(null);
    
      function handleClick() {
        var enteredAge = inputRef.current.value;
        if (enteredAge) {
          setage(enteredAge);
        }
      }
    
      return (
        <div>
          Enter Your Age:
          <input type="text" id="txtAge" name="age" ref={inputRef} />
          <button onClick={handleClick}> Check </button>
          <label id="lblResult">
            {age > 50
              ? `"Wow ${age}! You're old, rest up!`
              : `Wow ${age}! Young gun!`}
          </label>
        </div>
      );
    };
    
    export default MyState;