prs99
prs99

Reputation: 71

Waiting for react state to update in function component

How can I make sure that handleOpen only executes after all the states (nameError, emailError, messageError) have been updated? My problem is that since state doesn't update immediately, sometimes handleOpen executes when it shouldn't.

const handleSend = (e) => {
e.preventDefault();
if (name === "") {
  setNameError(true);
}
if (email === "") {
  setEmailError(true);
  setEmailErrorMessage("Please type your email!");
}
if (!email.includes("@")) {
  setEmailError(true);
  setEmailErrorMessage("Invalid email address");
}
if (message === "") {
  setMessageError(true);
}
if (!nameError && !emailError && !messageError) {
  handleOpen();
}

};

Upvotes: 3

Views: 201

Answers (4)

Shayan Faghani
Shayan Faghani

Reputation: 240

The best scenario you could follow for these kinds of problems is that you create another state or even a constant that has the checks for the validation and then put it in the useEffect to check if the value is changed and then whenever it is changed, you check it if it holds true or no. for better understanding you can do like this:

const [wholeValidate, setWholeValidate] = useState(false);

const handleSend = (e) => {
e.preventDefault();
if (name === "") {
  setNameError(true);
}
if (email === "") {
  setEmailError(true);
  setEmailErrorMessage("Please type your email!");
}
if (!email.includes("@")) {
  setEmailError(true);
  setEmailErrorMessage("Invalid email address");
}
if (message === "") {
  setMessageError(true);
}

if (!nameError && !emailError && !messageError) {
  setWholeValidate(true);  
}

useEffect(() => {
  if(wholeValidate) {handleOpen();}
}, [wholeValidate])

Upvotes: 0

hmak
hmak

Reputation: 3968

I see that you are struggling with JS Event Loop. React states will update after an event loop is finished. So you can't expect them to update immediately in your handler function.

However there are some other solutions like using setTimeout(..., 0) to cheat the event loop, which is not going to help you in this case.

Here you need to refactor the handler method to handle the error states in your function scope and set the final result to the error states.

const handleSend = (e) => {
  e.preventDefault();
  
  const errors = {
    name: '',
    email: '',
    message: '',
  };
  
  if (name === "") {
    errors.name = "Please enter your name";
  }

  if (email === "") {
    errors.email = "Please type your email!";
  }

  if (!email.includes("@")) {
    errors.email = "Invalid email address";
  }

  if (message === "") {
    errors.email = "Please enter a message";
  }

  setNameError(!!errors.name);
  setNameErrorMessage(errors.name);
  setEmailError(!!errors.email);
  setEmailErrorMessage(errors.email);
  setMessageError(!!errors.message);
  setMessageErrorMessage(errors.message);

  if (Object.values(errors).filter(Boolean).length === 0) {
     handleOpen();
  }
}

I strongly suggest refactoring the error handling process in your application. Your solution and the one I wrote here is not efficient at all.

Good Luck ;)

Upvotes: 1

maxpsz
maxpsz

Reputation: 604

I would do a whole refactor on how you are handling the errors, but answering your question, you could do just this:

const handleSend = (e) => {
    e.preventDefault();

    const isNameEmpty = name === "";
    const isEmailEmpty = email === "";
    const isEmailAtMissing = !email.includes("@");
    const isMessageEmpty = message === "";

    setNameError(isNameEmpty);
    setEmailError(isEmailEmpty || isEmailAtMissing);
    setEmailErrorMessage(isEmailEmpty ? "Please type your email!" : "Invalid email address");
    setMessageError(isMessageEmpty);

    if ([isNameEmpty, emailError, emailError].some(value => !!value)) {
        handleOpen()
    }
}

If you save the "errors" in variables, then you make sure you are checking the last values you are using in the states.

Upvotes: 0

ndotie
ndotie

Reputation: 2150

You can use react's useRef hook, for its not reactive, i.e doesnt cause page rerender so that you're sure

if (!nameError && !emailError && !messageError) {
  handleOpen();
}

executes when the right in the order you wanted just as i'll show below

const setNameError = useRef(null);
const setEmailError = useRef({value : false, message : ''});
const setMessageError = useRef(null);
const handleSend = (e) => {
e.preventDefault();
if (name === "") {
  setNameError.current = true;
}
if (email === "") {
  setEmailError.current.value = true;
  setEmailError.current.message = "Please type your email!";
}
if (!email.includes("@")) {
  setEmailError.current.value = true;
  setEmailError.current.message = "Invalid email address";
}
if (message === "") {
  setMessageError.current = true;
}
if (!setNameError.current && !setRmailError.current && !setMessageError.current) {
  handleOpen();
}
};

i have used bad naming conversion so as to go with your example, but in real life you wont chose setNameError for a variable name as I've done but maybe you'll do nameError

Upvotes: 0

Related Questions