'React - Checking for a value in an uncontrolled input?

I've got a custom Input control, which I'd like to be uncontrolled. I don't care what the user writes or where this is stored. However, I need to know if the input has some value or not, because depending on that it will have an appearance or another (such as a floating label).

Since it's not possible to check purely by CSS if an input has a value or not (because the attribute value does not get updated when the user types and changes its value), the only way to know if it has a value or not is programatically.

If I use useState to keep a local state of its value just to know if it has some (though it keeps being uncontrolled), whenever some external script changes the value of the input using ref (for example, react-hook-forms reset function), it won't get updated because the state won't notice the change, and thus the input will be empty but its state will think it still has a value.

As it's not a possibility, then, I wanted to access and watch for the ref .value changes. However, it doesn't change (or, at least, it doesn't notify the change). If I access the value prop directly, it has the correct value, but it doesn't trigger the useEffect although what I'm using as a dependency is the value and not the ref itself.

  const [hasValue, setHasValue] = useState(false);

  useEffect(() => {
    setHasValue(!!inputRef?.current?.value);
  }, [inputRef?.current?.value]);

Here is Codesandbox a sample of what I'm referring.

Is there any way to check the actual value of the input inside my custom component without having to send a value prop from the parent, which is able to react to external changes by ref, too? So, no matter how the input value changes, it's aware of it.



Solution 1:[1]

You could use Inder's suggestion with vanilla javascript or you could trigger your useEffect with afterValue, which gets set onChange.

import React, { useEffect, useRef, useState } from "react";

const Input = () => {
  const inputRef = useRef(null);
  const [afterValue, setAfterValue] = useState("");
  const [hasValue, setHasValue] = useState(false);

  useEffect(() => {
    setHasValue(!!inputRef?.current?.value);
  }, [afterValue]);

  return (
    <div>
      <input
        ref={inputRef}
        onChange={({ target: { value } }) =>
          setTimeout(() => setAfterValue(value), 1000)
        }
      />
      <br />
      <br />
      <div>Has value: {hasValue.toString()}</div>
      <div>Actual value: {inputRef?.current?.value}</div>
      <div>After value: {afterValue}</div>
    </div>
  );
};

export default Input;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 RyanY