Next.js Discord

Discord Forum

How would you restructure this?

Answered
Polar bear posted this in #help-forum
Open in Discord
Polar bearOP
I'm trying to create a login form that has both client and server side validation (and displays errors for both of them)
The problem I have is i want to redirect users on successful login to "/" but i also need to return null error message to the client ( if i don't It will give me cannot read undefined error as I'm not returning anything )

I can't return this res because redirect causes unreachable code below it.

LoginForm:
"use client";

import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";

import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PasswordInput } from "../ui/password-input";

import { login, signup } from "@/actions/login/actions";

import { loginFormSchema } from "@/lib/zodSchemas";

export default function LoginForm() {
  const form = useForm<z.infer<typeof loginFormSchema>>({
    resolver: zodResolver(loginFormSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

  const onSubmitLogin = async (values: z.infer<typeof loginFormSchema>) => {
    const res = await login(values);
    if (res.errorMessage) {
      form.setError("root", {
        type: "manual",
        message: res.errorMessage,
      });
    }
  };

  const onSubmitCreateNewAccount = async (
    values: z.infer<typeof loginFormSchema>,
  ) => {};

  return (
    <Form {...form}>
      <form className="flex flex-col gap-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="example@mail.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Password</FormLabel>
              <FormControl>
                <PasswordInput {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <div className="mt-4 flex flex-col gap-4">
          <Button onClick={form.handleSubmit(onSubmitCreateNewAccount)}>
            Register
          </Button>
          <Button variant="outline" onClick={form.handleSubmit(onSubmitLogin)}>
            Login
          </Button>
        </div>
        <p className="text-center text-sm font-medium text-destructive">
          {form.formState.errors?.root?.message}
        </p>
      </form>
    </Form>
  );
}

actions.ts
"use server"

import { createClient } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import { redirect, useRouter } from "next/navigation";

import { loginFormSchema } from "@/lib/zodSchemas";
import { z } from "zod";

export async function login(loginData: z.infer<typeof loginFormSchema>) {
  const supabase = createClient();
  const safeParsedData = loginFormSchema.safeParse(loginData);

  const res = {
    errorMessage: "",
  };

  if (!safeParsedData.success) {
    return {errorMessage: "Something went wrong, please try again."};
  }

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

  if (error) {
    return {errorMessage: error.message};
  }

  revalidatePath("/");
  redirect("/");
  // unreachable code
  return res;
}

export async function signup(loginData: z.infer<typeof loginFormSchema>) {
  ...
}

export async function logout() {
...
}
Answered by Polar bear
I got it.

export async function login(loginData: z.infer<typeof loginFormSchema>) {
  const supabase = createClient();
  const safeParsedData = loginFormSchema.safeParse(loginData);

  if (!safeParsedData.success) {
    return {errorMessage: "Something went wrong, please try again."};
  }

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

  if (error) {
    return {errorMessage: error.message};
  }

  revalidatePath("/");
  redirect("/");
}


const onSubmitLogin = async (values: z.infer<typeof loginFormSchema>) => {
    const res = await login(values);

    if (res?.errorMessage) {
      // If there's an error message, display it
      form.setError("root", {
        type: "manual",
        message: res.errorMessage,
      });
    }
  };
View full answer

9 Replies

Polar bearOP
I know I could redirect on client side but I would like to keep the login logic in one place
Polar bearOP
I got it.

export async function login(loginData: z.infer<typeof loginFormSchema>) {
  const supabase = createClient();
  const safeParsedData = loginFormSchema.safeParse(loginData);

  if (!safeParsedData.success) {
    return {errorMessage: "Something went wrong, please try again."};
  }

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

  if (error) {
    return {errorMessage: error.message};
  }

  revalidatePath("/");
  redirect("/");
}


const onSubmitLogin = async (values: z.infer<typeof loginFormSchema>) => {
    const res = await login(values);

    if (res?.errorMessage) {
      // If there's an error message, display it
      form.setError("root", {
        type: "manual",
        message: res.errorMessage,
      });
    }
  };
Answer
Polar bearOP
It works fine but if you have any suggestions on refactoring this code I would love to hear you out
Mini Satin
action.ts
"use server";

import { createClient } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { loginFormSchema } from "@/lib/zodSchemas";
import { z } from "zod";

export async function login(loginData: z.infer<typeof loginFormSchema>) {
const supabase = createClient();
const safeParsedData = loginFormSchema.safeParse(loginData);

if (!safeParsedData.success) {
return { errorMessage: "Invalid input. Please check your credentials." };
}

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

if (error) {
return { errorMessage: error.message };
}

revalidatePath("/"); // This can still be used for cache revalidation
return { success: true }; // Indicate successful login
}
LoginForm.ts
"use client";

import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation"; // Add this import

import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PasswordInput } from "../ui/password-input";
import { login } from "@/actions/login/actions";
import { loginFormSchema } from "@/lib/zodSchemas";

export default function LoginForm() {
const form = useForm<z.infer<typeof loginFormSchema>>({
resolver: zodResolver(loginFormSchema),
defaultValues: {
email: "",
password: "",
},
});

const router = useRouter(); // Initialize the router

const onSubmitLogin = async (values: z.infer<typeof loginFormSchema>) => {
const res = await login(values);
if (res.errorMessage) {
form.setError("root", {
type: "manual",
message: res.errorMessage,
});
} else {
// Redirect on successful login
router.push("/"); // Redirect to the home page
}
};

const onSubmitCreateNewAccount = async (values: z.infer<typeof loginFormSchema>) => {
// Handle account creation
};

return (
<Form {...form}>
<form className="flex flex-col gap-4" onSubmit={form.handleSubmit(onSubmitLogin)}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="example@mail.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<PasswordInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="mt-4 flex flex-col gap-4">
<Button onClick={form.handleSubmit(onSubmitCreateNewAccount)}>
Register
</Button>
<Button variant="outline" type="submit"> {/* Change this button to submit the form */}
Login
</Button>
</div>
<p className="text-center text-sm font-medium text-destructive">
{form.formState.errors?.root?.message}
</p>
</form>
</Form>
);
}