Next.js Discord

Discord Forum

login form in nextjs 14

Answered
Oak apple gall wasp posted this in #help-forum
Open in Discord
Oak apple gall waspOP
I have a login form - I want to create a popup message when you try to login that indicates if you have an incorrect password.

I have the following:

<form action={loginForm} className="space-y-4">
        <CardContent className="p-6">
          <div className="space-y-4">
            <div className="space-y-2">
              <Label className="text-base" htmlFor="email">
                Email
              </Label>
              <Input
                  className="mt-1.5"
                  id="email"
                  placeholder="m@example.com"
                  required
                  type="email"/>
            </div>
            <div className="space-y-2">
              <div className="flex items-center">
                <Label className="text-base" htmlFor="password">
                  Password
                </Label>
                <Link className="ml-auto underline text-sm" href="#">
                  Forgot your password?
                </Link>
              </div>
              <Input className="mt-1.5" id="password" required type="password"/>
            </div>
            <Button type="submit" className="w-full" size="lg">
              Login
            </Button>
          </div>
        </CardContent>
      </form>


I'd want to make the loginForm function return if the login is correct, and if it is incorrect I want a notification to display that indicates "invalid username or password" but I'm not sure how to do that in Nextjs14 - any help would be much appreciated!
Answered by Broad-snouted Caiman
Well, I could share I code I've done before, if you want:

async function onSubmit(values: LoginInput) {
    const parse = LoginSchema.safeParse(values)
    if (parse.success) {
      setIsLoading(true)
      const res = await signIn(values)
      const { error } = JSON.parse(res)
      if (error === null) {
        toast({
          title: 'Login successful.',
          description: 'Welcome!',
        })
        router.push('/')
      } else {
        setIsLoading(false)
        setErrorMsg('Invalid credentials.')
      }
    }
  }
View full answer

38 Replies

Broad-snouted Caiman
loginForm shouldn't return anything. It should be an async function in which you call the login function of your auth solution and if the auth was ok, then you redirect the user to where you want. If it wasn't ok, then you should notify the user (do this inside the same function).
A common way of notification, is to use toast.
Oak apple gall waspOP
makes sense! Is there anywhere I can see a simple barebones minimal example of this?
New to nextjs so still getting accustomed to the differences between react
Broad-snouted Caiman
Well, I could share I code I've done before, if you want:

async function onSubmit(values: LoginInput) {
    const parse = LoginSchema.safeParse(values)
    if (parse.success) {
      setIsLoading(true)
      const res = await signIn(values)
      const { error } = JSON.parse(res)
      if (error === null) {
        toast({
          title: 'Login successful.',
          description: 'Welcome!',
        })
        router.push('/')
      } else {
        setIsLoading(false)
        setErrorMsg('Invalid credentials.')
      }
    }
  }
Answer
Oak apple gall waspOP
That's helpful for sure, where are you getting setIsLoading / setErrorMsg? Are those useStates? I'd assume so, if so I'd assume the same issue would arise where I am importing useState and I get an error because it's defaulted to a server component not a client component and as far as I was aware for a form you need to use a server component
Oak apple gall waspOP
ooh, okay, I didn't know that worked, all of this is quite new/strange for me still as I make the transition to nextjs
Oak apple gall waspOP
so let's say I want to take the following component:

import { CardTitle, CardDescription, CardHeader, CardContent, Card } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import {LoginNotification} from "@/components/component/Pages/Login/loginNotification";


const displayNotification = false;
export function LoginMenu() {

  return (
    (<Card className="w-full max-w-md p-0">
      <CardHeader className="bg-gray-50 dark:bg-gray-900 border-b">
        <div className="p-6">
          <CardTitle className="text-3xl font-bold">Login</CardTitle>
          <CardDescription>Enter your email below to login to your account</CardDescription>
        </div>
      </CardHeader>
      <form action={loginForm} className="space-y-4">
        <CardContent className="p-6">
          {displayNotification && <LoginNotification />}
          <div className="space-y-4">
            <div className="space-y-2">
              <Label className="text-base" htmlFor="email">
                Email
              </Label>
              <Input
                  className="mt-1.5"
                  id="email"
                  placeholder="m@example.com"
                  required
                  type="email"/>
            </div>
            <div className="space-y-2">
              <div className="flex items-center">
                <Label className="text-base" htmlFor="password">
                  Password
                </Label>
                <Link className="ml-auto underline text-sm" href="#">
                  Forgot your password?
                </Link>
              </div>
              <Input className="mt-1.5" id="password" required type="password"/>
            </div>
            <Button type="submit" className="w-full" size="lg">
              Login
            </Button>
          </div>
        </CardContent>
      </form>
    </Card>
)
);
}


async function loginForm () {
        console.log('do stuff')
  }


Where would I indicate is client such that it allows me to import and use a useState? It previously was giving me an error
trying the following:

/**
 * This code was generated by v0 by Vercel.
 * @see https://v0.dev/t/Hh2MegMfgc6
 */
"use client"
import { CardTitle, CardDescription, CardHeader, CardContent, Card } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import {LoginNotification} from "@/components/component/Pages/Login/loginNotification";
import {useState} from "react";

const [displayNotification, setDisplayNotification] = useState("false")

export function LoginMenu() {

  return (
    (<Card className="w-full max-w-md p-0">
      <CardHeader className="bg-gray-50 dark:bg-gray-900 border-b">
        <div className="p-6">
          <CardTitle className="text-3xl font-bold">Login</CardTitle>
          <CardDescription>Enter your email below to login to your account</CardDescription>
        </div>
      </CardHeader>
          {displayNotification && <LoginNotification />}
      <form action={loginForm} className="space-y-4">
        <CardContent className="p-6">
          {displayNotification && <LoginNotification />}
          <div className="space-y-4">
            <div className="space-y-2">
              <Label className="text-base" htmlFor="email">
                Email
              </Label>
              <Input
                  className="mt-1.5"
                  id="email"
                  placeholder="m@example.com"
                  required
                  type="email"/>
            </div>
            <div className="space-y-2">
              <div className="flex items-center">
                <Label className="text-base" htmlFor="password">
                  Password
                </Label>
                <Link className="ml-auto underline text-sm" href="#">
                  Forgot your password?
                </Link>
              </div>
              <Input className="mt-1.5" id="password" required type="password"/>
            </div>
            <Button type="submit" className="w-full" size="lg">
              Login
            </Button>
          </div>
        </CardContent>
      </form>
    </Card>
)
);
}


async function loginForm () {
        setDisplayNotification(true)
  }
Broad-snouted Caiman
"use client" must be the first thing in order to make a client component.
const [displayNotification, setDisplayNotification] = useState("false")

This should be inside the LoginMenu component.
Oak apple gall waspOP
"use client"

import { CardTitle, CardDescription, CardHeader, CardContent, Card } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import {LoginNotification} from "@/components/component/Pages/Login/loginNotification";
import {useState} from "react";



export function LoginMenu() {

  const [displayNotification, setDisplayNotification] = useState("false")

  return (
    (<Card className="w-full max-w-md p-0">
      <CardHeader className="bg-gray-50 dark:bg-gray-900 border-b">
        <div className="p-6">
          <CardTitle className="text-3xl font-bold">Login</CardTitle>
          <CardDescription>Enter your email below to login to your account</CardDescription>
        </div>
      </CardHeader>
      <form action={loginForm} className="space-y-4">
        <CardContent className="p-6">
          {displayNotification && <LoginNotification />}
          <div className="space-y-4">
            <div className="space-y-2">
              <Label className="text-base" htmlFor="email">
                Email
              </Label>
              <Input
                  className="mt-1.5"
                  id="email"
                  placeholder="m@example.com"
                  required
                  type="email"/>
            </div>
            <div className="space-y-2">
              <div className="flex items-center">
                <Label className="text-base" htmlFor="password">
                  Password
                </Label>
                <Link className="ml-auto underline text-sm" href="#">
                  Forgot your password?
                </Link>
              </div>
              <Input className="mt-1.5" id="password" required type="password"/>
            </div>
            <Button type="submit" className="w-full" size="lg">
              Login
            </Button>
          </div>
        </CardContent>
      </form>
    </Card>
)
);
}


async function loginForm () {
        setDisplayNotification(true)
  }
Broad-snouted Caiman
async function loginForm () {
        setDisplayNotification(true)
  }

This should also be inside the LoginMenu component.
Oak apple gall waspOP
made the above changes, it still displays before the button is pressed implying the displayNotification bool is evaluating to truthy
Broad-snouted Caiman
The notification is appearing after you press F5 (reload the page) and before you press the button?
Oak apple gall waspOP
yeah it is displaying on refresh before the button is pressed
I just had a div containing "wow" to test it
Broad-snouted Caiman
This is wrong:
const [displayNotification, setDisplayNotification] = useState("false")
It should be
const [displayNotification, setDisplayNotification] = useState(false)
It should a boolean, not a string.
Oak apple gall waspOP
ah embarassing, good catch!
thank you! I'll look into the toast as well 🙂 Appreciate your help!
it's working now, thanks 🙂
@Oak apple gall wasp it's working now, thanks 🙂
Broad-snouted Caiman
I'm glad to hear it! Good luck to you. Don't forget to mark the answer as a solution, please 👍
Oak apple gall waspOP
How do I do that?
Broad-snouted Caiman
Hover the mouse over the message, it will appear 3 dots on the top-right corner. Go to Apps, then Mark Solution
Oak apple gall waspOP
ooh okay, I'm not sure which to mark since it was multiple parts
Oak apple gall waspOP
I marked the earlier snippet you linked
thanks 🙂
@Oak apple gall wasp thanks 🙂
Broad-snouted Caiman
You're welcome!
Oak apple gall waspOP
@Broad-snouted Caiman Hey! I don't know if you're still around but I had a followup question
I set up another form and routed it the same way, but when I try to use the values inside from the form I get

FormData { [Symbol(state)]: [] }

and don't see any other means the data is being passed
@Oak apple gall wasp <@1186364949631619182> Hey! I don't know if you're still around but I had a followup question
Broad-snouted Caiman
Hi there! I've followed these steps and recommend you to do the same: https://ui.shadcn.com/docs/components/form#usage