0

My fields in the form require that I focus on them for the error messages to appear. I want the error messages to occur when I click on submit when I haven't focused the fields yet.

Here's the hook (the doubt is about the setIsTouched call later):

const useInputModifed = (validationArray) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  //   [{errorName:,fn:validationFn}]
  let errorArray = [];

  for (const errorEntry of validationArray) {
    if (errorEntry.isErrorFn(enteredValue)) {
      errorArray.push(errorEntry.errorName);
    }
  }

  const hasError = errorArray.length > 0 && isTouched;

  const valueChangeHandler = (event) => {
    setEnteredValue(event.target.value);
  };

  const reset = () => {
    setEnteredValue("");
    setIsTouched(false);
  };

  console.log(errorArray, isTouched);

  return {
    value: enteredValue,
    validationArray: errorArray,
    hasError,
    valueChangeHandler,
    setIsTouched,
    isTouched,
    reset,
  };
};

Here's the form handler (I have used aliases for each field):

const formSubmissionHandler = (event) => {
    event.preventDefault();
    touchNameField(true);
    touchEmailField(true);


    if (nameInputHasError || emailInputHasError) {
 //console logging both fields gives false even after touching
      return;
    }
  
    if (!nameInputHasError && !emailInputHasError) {
      resetNameInput();
      resetEmailInput();
    }
  };

Take the validation that the name field is empty for example and I click the submit button in the beginning itself. Currently in const hasError = errorArray.length > 0 && isTouched; the array has elements but isTouched is false, as I did not focus the field.

So then the `touchNameField(true);' comes in. I try to make hasError true, but it's still showing false.

Is the fact that setState calls are asynchronous the issue? If I remove the reset calls it works. But I still don't understand why the nameInputError etc are false.

Reuben B
  • 184
  • 1
  • 6
  • you're pretty much already at the answer -> if `touchNameField` and `touchEmailField` are aliases for `setIsTouched`, then the touched state for the fields won't be reflective of that while it still finishing calling `formSubmissionHandler` because react hasn't rendered so your submission handler hasn't closed over the latest state yet – Tom Finney Jun 27 '23 at 13:37
  • this is a mix of javascript/closures + react so when it renders, `nameInputHasError` and `emailInputHasError` inside of the submission handler will be closed over their most recent values and they don't """update""" immediately once you call set state, it will finish calling your submission handler and _then_ react will process the state updates, render, and then they will be as you expect – Tom Finney Jun 27 '23 at 13:40
  • 1
    i put together a lil codesandbox showing one way of fixing it: https://codesandbox.io/s/currying-thunder-htpr67?file=/src/App.js we expose a function for getting the errors from the hook, so we just call that inside of the submit handler to check if we have any errors and respond accordingly, instead of relying on the value return from the hook. it's pretty simple and you might be able to more elegantly solve it with more state/refs or something but it should help understand what is going on – Tom Finney Jun 27 '23 at 14:06
  • @TomFinney Bingo! your code works! Thank you. Can you answer below so I can accept? I slightly understand the concept. I assumed that whenever set state occurs react re-renders, but this seems to be an exception because it re-renders at the wrong time? – Reuben B Jun 28 '23 at 09:48

1 Answers1

0

Without running it, I believe that's because errorArray and hasError are not reacting to changes. So it will also depends on where you are instantiating that hook. Without testing this, I would try something like:

const useInputModifed = (validationArray) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isTouched, setIsTouched] = useState(false);

  const [errorArray, setErrorArray] = useState([]);
  const [hasError, setHasError] = useState(false);

  useEffect(() => {
    const newErrorArray = [];
    for (const errorEntry of validationArray) {
      if (errorEntry.isErrorFn(enteredValue)) {
        newErrorArray.push(errorEntry.errorName);
      }
    }
    setErrorArray(newErrorArray);
  }, [validationArray, enteredValue])

  useEffect(() => {
    setHasError(errorArray.length && isTouched);
  }, [errorArray, isTouched])

  const valueChangeHandler = (event) => {
    setEnteredValue(event.target.value);
  };

  const reset = () => {
    setEnteredValue("");
    setIsTouched(false);
  };

  console.log(errorArray, isTouched);

  return {
    value: enteredValue,
    validationArray: errorArray,
    hasError,
    valueChangeHandler,
    setIsTouched,
    isTouched,
    reset,
  };
};

Again, not tested and you might need some adjustments.

Marco
  • 59
  • 10
  • given code causes an infinite loop which is solved by adding useMemo to validationArray. Though it still doesn't solve the problem I have, it doesn't render the errors on submission. It's not reacting. – Reuben B Jun 28 '23 at 09:23
  • Guesswork here but in the component importing the hook, do `hasError`, `isTouched` and `validationArray ` reflect what you would expect? If so the problem may be the component rendering. – Marco Jun 28 '23 at 09:58
  • I just realized @TomFinney answered above. Nice one +1 – Marco Jun 28 '23 at 10:02