How would you restructure this?
Answered
Polar bear posted this in #help-forum
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:
actions.ts
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,
});
}
};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
}
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>
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>
);
}
<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>
);
}
@Mini Satin "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
}
Polar bearOP
Your solution moves the logic of redirecting away from the actions which is something I would like to avoid and my solution does that.