Search code examples
reactjsreact-hook-formzod

Form submission not working with keyboard key - React Hook Form, Zod


I have a login form component where the user can log in using the 'submit' button or by pressing the 'enter' key. The problem is that when I try to submit the form using the 'Enter' key, the form validation fails somehow and I get an 'enter your password' error which indicates that I have not provided input data for the 'password' field when I in fact have.

Code:

custom hook:

import { useEffect } from "react";

export const useKeyDown = (callback, keys) => {
  const onKeyDown = (e) => {
    const wasSubmitKeyPressed = keys.some((key) => e.key === key);
    if (wasSubmitKeyPressed) {
      e.preventDefault();
      callback();
    }
  };

  useEffect(() => {
    document.addEventListener("keydown", onKeyDown);
    return () => {
      document.removeEventListener("keydown", onKeyDown);
    };
  }, [onKeyDown]);
};

login.tsx:

"use client";

import { useState } from "react";
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useRouter } from "next/navigation";
import { useForm, SubmitHandler } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import * as z from "zod";
import type { Database } from "lib/database.types";
import { BeatLoader } from "react-spinners";
import Image from "next/image";
import logo from "./../../public/logo.png";
import { useEffect } from "react";
import { useKeyDown } from "hooks/useKeyDown";

type Schema = z.infer<typeof schema>;

const schema = z.object({
  email: z
    .string()
    .nonempty({ message: "Enter your email" })
    .email({ message: "Invalid format!" }),
  password: z.string().nonempty({ message: "Enter your password" }),
  // .min(6, { message: "Password must be at least six characters!" }),
});

const Login = () => {
  const router = useRouter();
  const supabase = createClientComponentClient<Database>();
  const [loading, setLoading] = useState(false);
  const [message, setMessage] = useState("");
  const [isButtonFocused, setIsButtonFocused] = useState(false);
  const [redirectRoute, setRedirectRoute] = useState<string>("");
  const [isSubmitted, setIsSubmitted] = useState(false);

  let storedPathname;

  console.log("redirectRoute", redirectRoute);

  const {
    register,
    handleSubmit,
    formState: { errors },
    clearErrors,
    getValues,
    formState,
    trigger,
  } = useForm({
    defaultValues: { email: "", password: "" },
    resolver: zodResolver(schema),
  });

  console.log("formState", formState);

  const onSubmit: SubmitHandler<Schema> = async (data) => {
    console.log("errors", errors);
    setIsButtonFocused(true);
    setIsSubmitted(true);

    try {
      // Upon successful login, Redirect to the originally requested page or a default page

      // login
      const { error } = await supabase.auth.signInWithPassword({
        email: data.email,
        password: data.password,
      });

      if (error) {
        setIsButtonFocused(false);
        setMessage("An error occurred. " + error.message);
        return;
      }

      // redirect user to originally requested page

      // if (redirectRoute !== null) {
      router.push(redirectRoute);
    } catch (error) {
      setMessage("An error occurred. " + error);
      return;
    } finally {
      setLoading(false);
      router.refresh();
    }
  };

  // Define the keys array
  const keys = ["Enter"];

  // Use the useKeyDown hook to trigger form submission on Enter key press
  useKeyDown(() => handleSubmit(onSubmit)(), keys);

  useEffect(() => {
    storedPathname = localStorage.getItem("redirectRoute");
    // Retrieve stored pathname from ls
    // setRedirectRoute(currentPath);
    console.log("stored pathname: ", storedPathname);
    if (storedPathname !== null) {
      setRedirectRoute(storedPathname);
      console.log("PATHNAME SUCCESS!");
    } else {
      console.log("PATHNAME ERROR!");
      setRedirectRoute("/redirect-error-page");
    }

    // clear stored route after receiving it
    // localStorage.removeItem("redirectRoute");
  }, []);

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e);
  };

  return (
    <div className="h-screen pt-10 mt-16 lg:mt-0">
      <div className="flex justify-center">
        <Image src={logo} alt="logo" height={80} width={120} />
      </div>
      {/* <div className="text-center font-bold text-xl mb-10">login</div> */}
      <div className="max-w-[350px] mx-auto border border-gray-300 p-4 px-5">
        <div className="mt-4 mb-8 flex flex-col text-3xl font-semibold text-gray-800">
          Sign in
          <div className="mb-4 border border-gray-100 mt-4"></div>
        </div>
        <form onSubmit={handleSubmit(onSubmit)} className="mt-6">
          {/* email address */}
          <div className="mb-3">
            <input
              type="email"
              className="border rounded-md w-full py-2 px-3 focus:outline-none focus:border-emerald-500"
              placeholder="email address"
              id="email"
              {...register("email")}
              onChange={handleInputChange}
            />
            <div className="my-3 text-sm text-red-500">
              {errors.email?.message}
            </div>
          </div>

          {/* password */}
          <div className="mb-5">
            <input
              type="password"
              className="border rounded-md w-full py-2 px-3 focus:outline-none focus:border-emerald-500"
              placeholder="password"
              id="password"
              {...register("password")}
              onChange={handleInputChange}
            />
            <div className="my-3 text-sm text-red-500">
              {errors.password?.message}
            </div>
          </div>

          <div className="mb-5">
            {loading ? (
              <div className="w-full flex justify-center">
                <BeatLoader speedMultiplier={0.6} />
              </div>
            ) : (
              <button
                type="submit"
                className={`font-bold bg-amber-300 hover:brightness-95 focus:border focus:border-2 focus:border-emerald-500 focus:opacity-50 ${
                  isButtonFocused
                    ? "border border-2 border-emerald-500 opacity-50"
                    : ""
                } w-full rounded-md p-2 text-gray-600 text-sm`}
              >
                Sign in
              </button>
            )}
          </div>
        </form>

        {message && (
          <div className="my-5 text-center text-sm text-red-500">{message}</div>
        )}
        <div className="text-center text-sm mb-5">
          <Link
            href="/auth/reset-password"
            className="hover:underline text-blue-500"
          >
            Forgot your password?
            {/* Click here. */}
          </Link>
        </div>

        {/* <div className="text-center mb-2">New to Maui?</div> */}

        <div className="text-center text-sm border border-gray-700 rounded-md bg-stone-200 py-1">
          <Link href="/auth/signup" className="text-gray-700">
            Create an account
          </Link>
        </div>
      </div>
    </div>
  );
};

export default Login;

Solution

  • It's because you're overwriting the onChange that is returned from register.

                <input
                  {...register("password")}
                  onChange={handleInputChange} <- REMOVE THIS ON BOTH INPUTS
                />