mdoff's blog

React onChange does not show last typed character

· Tomasz Lewiński

Recently a colleague of mine ask me for a help with more less similar code:

const FormWithValidation = ({ onSubmit }) => {
  const [inputValue, setInputValue] = useState("");
  const [error, setError] = useState(false);
  const validate = () => {
    if (inputValue.length < 5) {
      // too short
      setError(true);
    } else {
      setError(false);
    }
  };
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        onSubmit({ inputValue });
      }}
    >
      <input
        style={error ? { border: "2px solid red" } : undefined}
        value={inputValue}
        onChange={(e) => {
          setInputValue(e.target.value);
          validate();
        }}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

At first sight code looked good, but there was a problem. When user typed 6 characters into input, there shouldn’t be any warning but the error was still there.

After logging a value of inputValue in validate function we discovered that indeed there was one character missing. It turns out that validate function is called with context when inputValue is not updated. There are many solutions to this problem:

  • pass e.target.value to validate function, and then we will have latest value of this input
  • make validate function return error value and use it in style prop
  • check validation inside useEffect hook (which we finally went with)

Now corrected version of this component looks like this:

const FormWithValidation = ({ onSubmit }) => {
  const [inputValue, setInputValue] = useState("");
  const [error, setError] = useState("");
  const validate = () => {
    if (inputValue.length < 5) {
      // too short
      setError(true);
    } else {
      setError(false);
    }
  };
  useEffect(() => {
    validate();
  }, [inputValue]);
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        onSubmit({ inputValue });
      }}
    >
      <input
        style={!!error ? { border: "2px solid red" } : undefined}
        value={inputValue}
        onChange={(e) => {
          setInputValue(e.target.value);
        }}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

This was really similar problem to this one found on StackOverflow.