Login authentication with axios and formik

Viewed 3231

I'm new to using both axios and formik and likely making a simple mistake here. I'm attempting to check if the user's email and password exists and has been typed correctly. Upon authentication they should be logged in with useContext and then redirected to the homepage.

Currently, after submitting, the form simply remains greyed out without reaching the .then/.catch block. Is it a problem with the way I typed the auth parameter?

const LoginForm = () => {
  const authenticate = useContext(AuthContext);
  const [serverState, setServerState] = useState();
  const handleServerResponse = (ok, msg) => {
    setServerState({ok, msg});
  };
  const handleOnSubmit = (values, actions) => {
    axios({
      method: "POST",
      url: "http://localhost:5000/api/users/login",
      data: values,
      auth: {
        email,
        password
      }
    })
    .then(response => {
      actions.setSubmitting(false);
      actions.resetForm();
      handleServerResponse(true, "Logged In!");
    })
    .catch(error => {
      actions.setSubmitting(false);
      handleServerResponse(false, error.response.data.error);
    });
    authenticate.login();
    Router.push("/")
  };

  return (
    <Formik
      initialValues={{
        email: "",
        password: "",
      }}
      validationSchema={Yup.object().shape({
        email: Yup.string(),
        password: Yup.string(),
      })}
      onSubmit={handleOnSubmit}
    >
      {({ isSubmitting }) => (
      <Form>
        <Field
          name="email"
          type="email"
          fullWidth
          component={TextField}
          variant="outlined"
          label="Email"
        />
        <Box pt={1}>
        <Field
          name="password"
          type="password"
          fullWidth
          component={TextField}
          variant="outlined"
          label="Password"
        />
        </Box>
        <Box pt={2}>
          <Button
            type="submit"
            variant="contained"
            fullWidth
            color="primary"
            disabled={isSubmitting}
          >
            Submit
          </Button>
          {serverState && (
          <Typography className={!serverState.ok ? "errorMsg" : ""}>
            {serverState.msg}
          </Typography>
        )}
        </Box>
      </Form>
      )}
    </Formik>
  );
};

The Node JS API on the backend:

const login = async (req, res, next) => {
  const { email, password } = req.body;

  let existingUser;

  try {
    existingUser = await User.findOne({ email: email });
  } catch (err) {
    const error = new HttpError("Login failed, please try again later.", 500);
    return next(error);
  }

  if (!existingUser || existingUser.password !== password) {
    const error = new HttpError("Invalid credentials, login failed.", 401);
    return next(error);
  }

  res.json({ message: "Logged in!" });
};
1 Answers

The reason why the form is grayed out is because you use isSubmitting and when you submit the form, it is set to true, but inside handleOnSubmit, you have an synchronous function, so you only call actions.setSubmitting(false) once the formik already think you finished the onSubmit.

Another way of doing this is to make handleOnSubmit return a promise and once the promise is resolved, formik will set isSubmitting to false automatically.

This is explained in the docs

IMPORTANT: If onSubmit is async, then Formik will automatically set isSubmitting to false on your behalf once it has resolved. This means you do NOT need to call formikBag.setSubmitting(false) manually. However, if your onSubmit function is synchronous, then you need to call setSubmitting(false) on your own.

So what I recomendo you to do and solve your problem is make handleOnSubmit an async method and return the axios call (return a promise) or use await.

An example of that would be something like

const handleOnSubmit = (values, actions) => {
  // returning a promise
  return axios({
    method: "POST",
    url: "http://localhost:5000/api/users/login",
    data: values,
    auth: {
      email,
      password
    }
  })
  .then(response => {
    actions.setSubmitting(false);
    actions.resetForm();
    handleServerResponse(true, "Logged In!");
  })
  .catch(error => {
    actions.setSubmitting(false);
    handleServerResponse(false, error.response.data.error);
  });

  // this shouldn't be outside the .then/.catch
  // if you are going to use .then/.catch, put the above line inside it
  // authenticate.login();
  // Router.push("/")
};

Or with async/await

//               using async
const handleOnSubmit = async (values, actions) => {

  //           using await in the axios call
  try {
    const response = await axios({
      method: "POST",
      url: "http://localhost:5000/api/users/login",
      data: values,
      auth: {
        email,
        password
      }
    })
    actions.setSubmitting(false);
    actions.resetForm();
    handleServerResponse(true, "Logged In!");
  } catch(error) {
    actions.setSubmitting(false);
    handleServerResponse(false, error.response.data.error);
  }
  authenticate.login();
  Router.push("/")
};
Related