0

I'm using react-hook-form, and I'm wondering if it's possible to create a component that is passing the form errors using react-hook-form.

I have the following component

import { useForm } from "react-hook-form";
import { XIcon } from "@heroicons/react/outline";

export default function ErrorsPopup() {
  const {
    formState: { errors },
  } = useForm({
    criteriaMode: "all",
  });

  return (
    <>
      {Object.keys(errors).length > 0 && (
        <div className="mt-6 text-white bg-red-600 border-4 border-red-700 alert-start">
          <div className="flex justify-between items-center bg-red-700 py-2 px-4">
            <div>
              <p>The following errors have occurred:</p>
            </div>

            <div className="cursor-pointer">
              <XIcon className="h-6 w-6 text-white alert-close" />
            </div>
          </div>

          <div className="py-2 px-4">
            <div className="flex flex-col gap-2">
              {Object.entries(errors).map(([index, error]) => (
                <div className="flex flex-col gap-y-6 sm:gap-x-8" key={index}>
                  <p>&bull; {error.message}</p>
                </div>
              ))}
            </div>
          </div>
        </div>
      )}
    </>
  );
}

and I have added this to the following page

import { useState } from "react";
import Head from "next/head";
import Link from "next/link";
import Image from "next/image";
import Background from "../../../public/images/option1.png";
import Router from "next/router";
import { signIn } from "next-auth/client";
import { useForm } from "react-hook-form";
import { SaveIcon } from "@heroicons/react/outline";

import ErrorsPopup from "../../components/ErrorsPopup";

export default function Login() {
  const { register, handleSubmit } = useForm();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const onSubmit = async () => {
    await signIn("credentials", {
      redirect: false,
      email: email,
      password: password,
    });

    Router.push("/dashboard");
  };

  return (
    <>
      <Head>
        <title>Ellis Development - Login</title>
      </Head>

      <div className="relative">
        <div className="md:flex">
          {/* Image */}
          <div className="flex items-center justify-center bg-blue-700 h-screen lg:w-96">
            <Image src={Background} width={350} height={350} layout="fixed" />
          </div>

          {/* Contact form */}
          <div className="flex flex-col justify-center py-10 px-6 sm:px-10 w-full">
            <h1 className="text-4xl font-extrabold text-grey-800">Login</h1>

            {/* errors */}
            <ErrorsPopup />

            <form
              onSubmit={handleSubmit(onSubmit)}
              className="mt-6 flex flex-col gap-y-6 sm:gap-x-8"
            >
              {/* email field */}
              <div>
                <label
                  htmlFor="email"
                  className="block text-sm font-medium text-gray-900"
                >
                  Email
                </label>

                <div className="mt-1">
                  <input
                    {...register("email", { required: "E-mail is required" })}
                    type="text"
                    name="email"
                    id="email"
                    className="py-3 px-4 block w-full shadow-sm text-gray-900 focus:ring-blue-700 focus:border-blue-900 border-gray-300 rounded-md"
                    onChange={(event) => setEmail(event.target.value)}
                  />
                </div>
              </div>

              {/* password field */}
              <div>
                <label
                  htmlFor="password"
                  className="block text-sm font-medium text-gray-900"
                >
                  Password
                </label>

                <div className="mt-1">
                  <input
                    {...register("password", {
                      required: "Password is required",
                    })}
                    type="password"
                    name="password"
                    id="password"
                    className="py-3 px-4 block w-full shadow-sm text-gray-900 focus:ring-blue-700 focus:border-blue-900 border-gray-300 rounded-md"
                    onChange={(event) => setPassword(event.target.value)}
                  />
                </div>
              </div>

              <div className="flex items-center justify-between sm:col-span-2">
                <div>
                  <button
                    type="submit"
                    className="mt-2 mr-2 w-full inline-flex items-center justify-center px-6 py-3 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-blue-700 hover:bg-blue-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-700 sm:w-auto"
                  >
                    <SaveIcon className="w-4 h-4 mr-4" />
                    Login
                  </button>
                </div>

                <div>
                  <Link href="/dashboard/auth/register">
                    <a className="underline decoration-blue-500 decoration-4 hover:decoration-2 mr-4">
                      Register
                    </a>
                  </Link>

                  <Link href="/dashboard/auth/forgot">
                    <a className="underline decoration-blue-500 decoration-4 hover:decoration-2">
                      Forgot your password?
                    </a>
                  </Link>
                </div>
              </div>
            </form>
          </div>
        </div>
      </div>
    </>
  );
}

So I'm trying to create a component for all forms where I can simply just show any errors to keep the code DRY, but the component is not showing up.

Sure I'm missing something silly but I'm new to react so any help would be create.

2 Answers 2

1

You've have to use FormProvider in your Login component and useFormContext in your ErrorPopup

👉🏻 https://react-hook-form.com/api/useformcontext

I've added a comment // 👈 where I've made changes.

Login.js

import { useState } from "react";
import Head from "next/head";
import Link from "next/link";
import Image from "next/image";
import Background from "../../../public/images/option1.png";
import Router from "next/router";
import { signIn } from "next-auth/client";
import { useForm, FormProvider } from "react-hook-form"; //👈
import { SaveIcon } from "@heroicons/react/outline";

import ErrorsPopup from "../../components/ErrorsPopup";

export default function Login() {
  const methods = useForm(); //👈
const { register, handleSubmit } = methods; //👈

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const onSubmit = async () => {
    await signIn("credentials", {
      redirect: false,
      email: email,
      password: password,
    });

    Router.push("/dashboard");
  };

  return (
    <>
      <Head>
        <title>Ellis Development - Login</title>
      </Head>

      <div className="relative">
        <div className="md:flex">
          {/* Image */}
          <div className="flex items-center justify-center bg-blue-700 h-screen lg:w-96">
            <Image src={Background} width={350} height={350} layout="fixed" />
          </div>

          {/* Contact form */}
          <div className="flex flex-col justify-center py-10 px-6 sm:px-10 w-full">
            <h1 className="text-4xl font-extrabold text-grey-800">Login</h1>
        
           <FormProvider {...methods}>  //👈
            {/* errors */}
            <ErrorsPopup />

            <form
              onSubmit={handleSubmit(onSubmit)}
              className="mt-6 flex flex-col gap-y-6 sm:gap-x-8"
            >
              {/* email field */}
              <div>
                <label
                  htmlFor="email"
                  className="block text-sm font-medium text-gray-900"
                >
                  Email
                </label>

                <div className="mt-1">
                  <input
                    {...register("email", { required: "E-mail is required" })}
                    type="text"
                    name="email"
                    id="email"
                    className="py-3 px-4 block w-full shadow-sm text-gray-900 focus:ring-blue-700 focus:border-blue-900 border-gray-300 rounded-md"
                    onChange={(event) => setEmail(event.target.value)}
                  />
                </div>
              </div>

              {/* password field */}
              <div>
                <label
                  htmlFor="password"
                  className="block text-sm font-medium text-gray-900"
                >
                  Password
                </label>

                <div className="mt-1">
                  <input
                    {...register("password", {
                      required: "Password is required",
                    })}
                    type="password"
                    name="password"
                    id="password"
                    className="py-3 px-4 block w-full shadow-sm text-gray-900 focus:ring-blue-700 focus:border-blue-900 border-gray-300 rounded-md"
                    onChange={(event) => setPassword(event.target.value)}
                  />
                </div>
              </div>

              <div className="flex items-center justify-between sm:col-span-2">
                <div>
                  <button
                    type="submit"
                    className="mt-2 mr-2 w-full inline-flex items-center justify-center px-6 py-3 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-blue-700 hover:bg-blue-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-700 sm:w-auto"
                  >
                    <SaveIcon className="w-4 h-4 mr-4" />
                    Login
                  </button>
                </div>

                <div>
                  <Link href="/dashboard/auth/register">
                    <a className="underline decoration-blue-500 decoration-4 hover:decoration-2 mr-4">
                      Register
                    </a>
                  </Link>

                  <Link href="/dashboard/auth/forgot">
                    <a className="underline decoration-blue-500 decoration-4 hover:decoration-2">
                      Forgot your password?
                    </a>
                  </Link>
                </div>
              </div>
            </form>
</FormProvider> //👈
          </div>
        </div>
      </div>
    </>
  );
}

ErrorPopup.js

import { useFormContext } from "react-hook-form"; // 👈
import { XIcon } from "@heroicons/react/outline";

export default function ErrorsPopup() {
  const {
    formState: { errors },
  } = useFormContext({ // 👈
    criteriaMode: "all",
  });

  return (
    <>
      {Object.keys(errors).length > 0 && (
        <div className="mt-6 text-white bg-red-600 border-4 border-red-700 alert-start">
          <div className="flex justify-between items-center bg-red-700 py-2 px-4">
            <div>
              <p>The following errors have occurred:</p>
            </div>

            <div className="cursor-pointer">
              <XIcon className="h-6 w-6 text-white alert-close" />
            </div>
          </div>

          <div className="py-2 px-4">
            <div className="flex flex-col gap-2">
              {Object.entries(errors).map(([index, error]) => (
                <div className="flex flex-col gap-y-6 sm:gap-x-8" key={index}>
                  <p>&bull; {error.message}</p>
                </div>
              ))}
            </div>
          </div>
        </div>
      )}
    </>
  );
}
Sign up to request clarification or add additional context in comments.

2 Comments

I get ReferenceError: methods is not defined after making the changes
My bad, edited Login component
0

I believe you need to use useFormContext instead of useForm in your ErrorsPopup component

https://react-hook-form.com/api/useformcontext

3 Comments

Would it be possible to provide an example based on the code above @Ray?
Sorry got caught up in a meeting but it seems that you got your answer already 👍
no problem @Ray :), cheers

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.