Can't stop refreshing even with e.preventDefault(), e.stopPropagation and ive also tried to do it using document.getElementById

Viewed 82

Here is my code for login page

import { useState } from "react";
import IsEmail from "isemail";
import { useRouter } from "next/router";
import m from "../library/magic-client";
import { useEffect } from "react";

const Login = () => {
  const [userMsg, setUserMsg] = useState("");
  const [email, setEmail] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const router = useRouter();
  const [_document, set_document] = useState(null);
  useEffect(() => {
    set_document(document);
  }, []);

  if (_document) {
    _document.getElementById("searchForm").addEventListener(
      "submit",
      function (e) {
        search(document.getElementById("searchText"));
        e.preventDefault();
      },
      false
    );
  }

  const handleOnChangeEmail = (e) => {
    e.preventDefault();
    e.stopPropagation();
    if (e.key === "Enter") {
      // Cancel the default action, if needed
      e.preventDefault();
      // Trigger the button element with a click
      document.getElementById("myBtn").click();
    }
    setUserMsg("");
    console.log("event", e);
    const email = e.target.value;
    setEmail(email);
  };
  const handleLoginWithEmail = async (e) => {
    e.preventDefault();
    e.stopPropagation();

    if (e.key === "Enter") {
      // Cancel the default action, if needed
      e.preventDefault();
      // Trigger the button element with a click
      document.getElementById("myBtn").click();
    }

    if (email) {
      if (IsEmail.validate(email)) {
        //router.push("/")
        try {
          setIsLoading(true);
          const didToken = await m.auth.loginWithMagicLink({ email });
          console.log({ didToken });
          if (didToken) {
            setIsLoading(false);
            router.push("/");
          }
        } catch {
          // Handle errors if required!
          console.error("something went wrong logging in");
          setIsLoading(false);
        }
      } else {
        setUserMsg("Enter a valid email address");
      }
    } else {
      //show usermssg
      setUserMsg("Enter an email address");
    }
  };

  return (
    <div
      className="bg-[url('/static/bglg.jpg')] flex items-stretch flex-col h-screen w-full"
      id="searchForm"
    >
      <head>
        <title>SiteName SignIn</title>
      </head>
      <header className="text-4xl px-10 py-2 font-black">
        <span className="text-indigo-700">SiteName</span>
      </header>
      <div className="w-full max-w-xs m-auto bg-[#C9C9C9] rounded p-5 bg-opacity-50 border-gray-200">
        <header>
          <div className="text-indigo-700 font-black text-3xl py-2">
            <p>{isLoading ? "Loading" : "Sign In"}</p>
          </div>
        </header>
        <form className="py-5">
          <div>
            <label className="block mb-2 text-indigo-500" for="username">
              Email
            </label>
            <input
              className="w-full p-2 mb-6 text-indigo-700 border-b-2 border-indigo-500 outline-none focus:bg-gray-300"
              type="text"
              name="username"
              placeholder="Email Address"
              onChange={handleOnChangeEmail}
            />
            <div className="block mb-2 text-red-700">
              <p>{userMsg}</p>
            </div>
          </div>
          <div>
            <input
              className="w-full bg-indigo-700 hover:bg-pink-700 text-white font-bold py-2 px-4 mb-6 rounded"
              type="button"
              value={isLoading ? "Loading" : "Sign In"}
              id="myBtn"
              onClick={handleLoginWithEmail}
            />
          </div>
        </form>
      </div>
    </div>
  );
};

export default Login;

There also seems to be some hydration error because the error happened outside of a Suspense boundary, causing the entire root will switch to client rendering. And this was happening way before i used any libraries if that is important.

My directories is from default create-next-app using npx.

1 Answers

You have a lot of unnecessary code and a memory leak. I suspect this is partly why your code isn't working "as expected."

Everything I'm doing below is using default HTML - that link has examples for on submit, required, autocomplete, and anything a form can do.


Your memory leak is caused by creating an event listener and not cleaning it up when the component unmounts.

You can prevent the memory leak by using the default HTML form onSubmit event listener and removing your custom event listener.

Remove:

  const [_document, set_document] = useState(null);
  useEffect(() => {
    set_document(document);
  }, []);

  if (_document) {
    _document.getElementById("searchForm").addEventListener(
      "submit",
      function (e) {
        search(document.getElementById("searchText"));
        e.preventDefault();
      },
      false
    );
  }

Add:

<form onSubmit={handleLoginWithEmail}>...

You can replace the Enter key check with a submit button that responds to the enter key press by default.

Remove:

e.preventDefault();
e.stopPropagation();
if (e.key === "Enter") {
 e.preventDefault();
 document.getElementById("myBtn").click();
}

Add:

<form ...>
 <button type="submit">log in</button>
</form>

All together:

I removed code that isn't relevant to make it cleaner.

You can clean up your checks by taking most of them out of if/else and making them if by utilizing return. You also have the opportunity for your loader never to stop, and it should be moved out of catch to finally so it's always called.

Also, iOS requires the action form attribute to be set, or it won't show the submit button on the on-screen keyboard.

const Login = () => {
  const [email, setEmail] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [userMsg, setUserMsg] = useState("");
  const router = useRouter();

  const handleOnChangeEmail = (e) => {
    setUserMsg("");
    setEmail(e.target.value);
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!email) return setUserMsg("Enter an email address");
    if (!IsEmail.validate(email)) return setUserMsg("Enter an email address");
    try {
      setIsLoading(true);
      const didToken = await m.auth.loginWithMagicLink({ email });
      if (didToken) router.push("/");
    } catch {
      console.error("Error logging in");
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form action="#" onSubmit={handleSubmit}>
      <input
        autoComplete="username"
        name="username"
        onChange={handleOnChangeEmail}
        required
        type="text"
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? "Loading" : "Sign In"}
      </button>
    </form>
  );
};
Related