Home > Back-end >  How to make React.useState work correctly in async function?
How to make React.useState work correctly in async function?

Time:10-31

I'm new to react and I'm trying to figure out how to make redirect after check if email is available or not. I've tried to do it with only React.useState, but states are not updated immediately, so I've been redirected in all cases.

I've read somewhere here that to change state I have to use React.useEffect, but after this my code doesn't redirect or output error message at all.

Here is my code with both useState and useEffect. How can I set state, so that the code can analyze the changes after?

import React from 'react';
import { ToMainNavPanel } from "./ToMainNavPanel";
import { Link, Navigate } from 'react-router-dom';
import axios from 'axios';
import '../css_components/sign-inup-form.css';

export function SignUpAccount() {

    const [passCoincidence, setPassCoincidence] = React.useState(false);
    const [emailExistance, setEmailExistance] = React.useState(false);
    const [canRedirect, setCanRedirect] = React.useState(false);

    let isEmailInvalid: boolean = false;
    let isRedirectAvailable: boolean = false;

    React.useEffect(() => {
        const tmpMail = isEmailInvalid;
        console.log(tmpMail);
        setEmailExistance(tmpMail);
    }, [isEmailInvalid]);

    React.useEffect(() => {
        const tmpRedirect = isRedirectAvailable;
        console.log(tmpRedirect);
        setCanRedirect(tmpRedirect);
    }, [isRedirectAvailable]);

    async function checkEmail(email: string) {
        await axios.post('https://api.escuelajs.co/api/v1/users/is-available', 
          {
            email: `${email}`
          })
          .then((response) => {
            if (!response.data.isAvailable) {
                isEmailInvalid = true;
            }
          })
          .catch((error) => {
            console.log(error);
        });
    }

    async function registerUser(name: string, email: string, pass: string) {
        await axios.post('https://api.escuelajs.co/api/v1/users/', 
          {
            name: `${name}`,
            email: `${email}`,
            password: `${pass}`,
            avatar: ""
          })
          .then((response) => {
            console.log(response);
          })
          .catch((error) => {
            console.log(error);
        });
    }

    async function checkEntered() {
        isEmailInvalid = false;
        isRedirectAvailable = false;
        
        let pass: string = (document.getElementById("newPass") as HTMLInputElement).value;
        let rPass: string = (document.getElementById("newPassRepeat") as HTMLInputElement).value;
        
        if (pass !== rPass) {
            setPassCoincidence(true);
            return;
        }
        else setPassCoincidence(false);

        let email: string = (document.getElementById("email") as HTMLInputElement).value;
        await checkEmail(email)
        .then(() => {
            if (!isEmailInvalid) {
                let name: string = (document.getElementById("user") as HTMLInputElement).value;
                registerUser(name, email, pass)
                .then(() => {
                    isRedirectAvailable = true;
                })
            }
        })
    }

    return (
        <div className='sign-up-page'>
            <ToMainNavPanel></ToMainNavPanel>
            <div className='entrance-window'>
                <div id="wrapper">
                    <form className='before:top-22' id="signin" method="" action="" onSubmit={(e) => e.preventDefault()}>
                        <input type="text" id="email" name="email" placeholder="Электронная почта" />
                        <input type="text" id="user" name="user" placeholder="Имя пользователя" />
                        <input type="password" id="newPass" name="newPass" placeholder="Придумайте пароль" />
                        <input type="password" id="newPassRepeat" name="newPassRepeat" placeholder="Повторите пароль" />
                        {passCoincidence && <p className='sign-error'>Entered passwords doesn't match!</p>}
                        {emailExistance && <p className='sign-error'>The email is already busy!</p>}
                        <p className='sign-text'><Link className='sign-link' to="/account">У меня уже есть аккаунт</Link></p>
                        <button className='top-15' type="submit" onClick={() => {checkEntered();}}>&#9998;</button>
                        {canRedirect && <Navigate replace to="/account"/>}
                    </form>
                </div>
            </div>
        </div>
    )
}

CodePudding user response:

why don't you return the value in your async method as well as set the state? That will allow you to get the returned value in your checkEntered method as well as update the state.

Here's an example of code that should do what you need (there may be better ways to achieve this though - if you use service classes that do the logic of checking for existing user and registering for you)

import React from 'react';
import { ToMainNavPanel } from "./ToMainNavPanel";
import { Link, Navigate } from 'react-router-dom';
import axios from 'axios';
import '../css_components/sign-inup-form.css';

export function SignUpAccount() {
  const [passCoincidence, setPassCoincidence] = React.useState(false);
  const [emailExistance, setEmailExistance] = React.useState(false);
  const [canRedirect, setCanRedirect] = React.useState(false);

  async function checkEmail(email: string): Promise<boolean> {
    await axios
      .post("https://api.escuelajs.co/api/v1/users/is-available", {
        email: `${email}`
      })
      .then((response) => {
        if (!response.data.isAvailable) {
          console.log("Email available: ", response.data.isAvailable);
          setEmailExistance(false);
          return false;
        }
      })
      .catch((error) => {
        console.log(error);
      });
    return true;
  }

  async function registerUser(name: string, email: string, pass: string) {
    await axios
      .post("https://api.escuelajs.co/api/v1/users/", {
        name: `${name}`,
        email: `${email}`,
        password: `${pass}`,
        avatar: ""
      })
      .then((response) => {
        console.log(response);
      })
      .catch((error) => {
        console.log(error);
      });
  }

  async function checkEntered() {
    let pass: string = (document.getElementById("newPass") as HTMLInputElement)
      .value;
    let rPass: string = (document.getElementById(
      "newPassRepeat"
    ) as HTMLInputElement).value;

    if (pass !== rPass) {
      setPassCoincidence(true);
      return;
    } else setPassCoincidence(false);

    let email: string = (document.getElementById("email") as HTMLInputElement)
      .value;
    await checkEmail(email).then((isEmailInvalid) => {
      if (!isEmailInvalid) {
        let name: string = (document.getElementById("user") as HTMLInputElement)
          .value;
        registerUser(name, email, pass).then(() => {
          setCanRedirect(true);
        });
      }
    });
  }

  return (
    <div className='sign-up-page'>
        <ToMainNavPanel></ToMainNavPanel>
        <div className='entrance-window'>
            <div id="wrapper">
                <form className='before:top-22' id="signin" method="" action="" onSubmit={(e) => e.preventDefault()}>
                    <input type="text" id="email" name="email" placeholder="Электронная почта" />
                    <input type="text" id="user" name="user" placeholder="Имя пользователя" />
                    <input type="password" id="newPass" name="newPass" placeholder="Придумайте пароль" />
                    <input type="password" id="newPassRepeat" name="newPassRepeat" placeholder="Повторите пароль" />
                    {passCoincidence && <p className='sign-error'>Entered passwords doesn't match!</p>}
                    {emailExistance && <p className='sign-error'>The email is already busy!</p>}
                    <p className='sign-text'><Link className='sign-link' to="/account">У меня уже есть аккаунт</Link></p>
                    <button className='top-15' type="submit" onClick={() => {checkEntered();}}>&#9998;</button>
                    {canRedirect && <Navigate replace to="/account"/>}
                </form>
            </div>
        </div>
    </div>
)
}

CodePudding user response:

If I'm understanding your question correctly you want to submit the form, run some asynchronous logic, then conditionally navigate. Refactor the checkEmail and regitserUser handlers to return their Promises. Don't mess with the local variables in the outer scope of the component function body, and don't mix async/await with Promise chains, this is often considered an anti-pattern. Use the useNavigate hook to access the navigate function to effect an imperative navigation action.

const navigate = useNavigate();

...

async function checkEmail(email: string) {
  const response = await axios.post(
    'https://api.escuelajs.co/api/v1/users/is-available', 
    { email: `${email}` });
  return !!response.data.isAvailable;
}

function registerUser(name: string, email: string, pass: string) {
  return axios.post('https://api.escuelajs.co/api/v1/users/', 
    {
      name,
      email,
      password: pass,
      avatar: ""
    }
  );
}

async function checkEntered() {
  const pass: string = (document.getElementById("newPass") as HTMLInputElement).value;
  const rPass: string = (document.getElementById("newPassRepeat") as HTMLInputElement).value;
    
  if (pass !== rPass) {
    setPassCoincidence(true);
    return;
  } else {
    setPassCoincidence(false)
  }

  const email: string = (document.getElementById("email") as HTMLInputElement).value;

  try {
    const isValidEmail = await checkEmail(email);

    if (isValidEmail) {
      const name: string = (document.getElementById("user") as HTMLInputElement).value;
      await registerUser(name, email, pass);
      navigate("/account", { replace: true });
    }
  } catch(error) {
    // handle error
  }
}
  • Related