I'm playing around with shadcn's react-hook-form and I've managed to navigate the docs to get the inputs and selects I need but the onSubmit function I've created isn't doing anything.
Does anyone know why?
"use client";
import * as z from "zod";
import { SubmitHandler, useForm, UseFormRegister } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Input } from "./ui/input";
import ErrorMessage from "./ErrorMessage";
import { Button } from "./ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "./ui/Form";
import { Textarea } from "./ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import toast from "react-hot-toast";
type ReminderFormSchemaType = z.infer<typeof reminderFormSchema>;
export const reminderFormSchema = z.object({
title: z.string().min(1, { message: "Title required!" }),
description: z.string().max(160),
location: z.string(),
recurring: z.boolean(),
recurringDigit: z.string(),
recurringString: z.string(),
priority: z.string(),
});
const CreateReminder = () => {
const form = useForm<ReminderFormSchemaType>({
resolver: zodResolver(reminderFormSchema),
});
function onSubmit(data: ReminderFormSchemaType) {
toast.success("success!");
}
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col w-full"
>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input placeholder="Create a title..." {...field} />
</FormControl>
<FormDescription>
{form.formState.errors.title && (
<ErrorMessage
message={form.formState.errors.title?.message}
/>
)}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Type your description here..."
{...field}
/>
</FormControl>
<FormDescription></FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row justify-between items-center gap-x-3">
<FormField
control={form.control}
name="recurringDigit"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-x-3">
<FormLabel>Remind me every: </FormLabel>
<Select onValueChange={field.onChange} defaultValue="1">
<FormControl>
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="1" />
</SelectTrigger>
</FormControl>
<SelectContent className="bg-lightbackground w-[100px]">
{Array.from(String(123456789)).map((value) => (
<SelectItem
key={value}
value={value}
className="cursor-pointer transition hover:bg-white hover:text-black"
>
{value}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItem>
)}
/>
<div className="flex-1">
<FormField
control={form.control}
name="recurringString"
render={({ field }) => (
<FormItem className="flex flex-row items-center flex-1 ">
<FormLabel></FormLabel>
<Select
onValueChange={field.onChange}
defaultValue="Minute(s)"
>
<FormControl>
<SelectTrigger className="flex-1 items-center">
<SelectValue placeholder="Minute(s)" />
</SelectTrigger>
</FormControl>
<SelectContent className="bg-lightbackground flex-1">
{["Day(s)", "Hour(s)", "Minute(s)"].map((value) => (
<SelectItem
key={value}
value={value}
className="cursor-pointer transition hover:bg-white hover:text-black"
>
{value}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItem>
)}
/>
</div>
</div>
<div>
<FormField
control={form.control}
name="priority"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-x-3">
<FormLabel>Priority: </FormLabel>
<Select onValueChange={field.onChange} defaultValue="High">
<FormControl>
<SelectTrigger className="flex-1">
<SelectValue placeholder="High" />
</SelectTrigger>
</FormControl>
<SelectContent className="bg-lightbackground flex-1">
{["Low", "Medium", "High"].map((value) => (
<SelectItem
key={value}
value={value}
className="cursor-pointer transition hover:bg-white hover:text-black"
>
{value}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<Button type="submit" className="bg-lightbackground my-3">
Submit
</Button>
</form>
</Form>
);
};
export default CreateReminder;
I am getting an error I don't understand if that might be useful:
app-index.js:32 Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
at input
at _c (webpack-internal:///(app-client)/./components/ui/input.tsx:13:11)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-slot/dist/index.mjs:46:23)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-slot/dist/index.mjs:20:23)
at eval (webpack-internal:///(app-client)/./components/ui/Form.tsx:124:14)
at div
at eval (webpack-internal:///(app-client)/./components/ui/Form.tsx:74:11)
at Controller (webpack-internal:///(app-client)/./node_modules/react-hook-form/dist/index.esm.mjs:544:37)
at FormField (webpack-internal:///(app-client)/./components/ui/Form.tsx:29:14)
at form
at FormProvider (webpack-internal:///(app-client)/./node_modules/react-hook-form/dist/index.esm.mjs:180:13)
at CreateReminder (webpack-internal:///(app-client)/./components/CreateReminder.tsx:53:75)
at div
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-primitive/dist/index.mjs:44:26)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-dismissable-layer/dist/index.mjs:44:42)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-slot/dist/index.mjs:46:23)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-slot/dist/index.mjs:20:23)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-primitive/dist/index.mjs:44:26)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-focus-scope/dist/index.mjs:32:19)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-dialog/dist/index.mjs:274:28)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-dialog/dist/index.mjs:206:102)
at $921a889cee6df7e8$export$99c2b779aa4e8b8b (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-presence/dist/index.mjs:28:22)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-dialog/dist/index.mjs:191:108)
at div
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-slot/dist/index.mjs:46:23)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-slot/dist/index.mjs:20:23)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-primitive/dist/index.mjs:44:26)
at eval (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-portal/dist/index.mjs:24:24)
at $921a889cee6df7e8$export$99c2b779aa4e8b8b (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-presence/dist/index.mjs:28:22)
at Provider (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-context/dist/index.mjs:47:28)
at $5d3850c4d0b4e6c7$export$dad7c95542bacce0 (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-dialog/dist/index.mjs:133:28)
at DialogPortal (webpack-internal:///(app-client)/./components/ui/dialog.tsx:25:11)
at _c2 (webpack-internal:///(app-client)/./components/ui/dialog.tsx:60:11)
at Provider (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-context/dist/index.mjs:47:28)
at $5d3850c4d0b4e6c7$export$3ddf2d174ce01153 (webpack-internal:///(app-client)/./node_modules/@radix-ui/react-dialog/dist/index.mjs:77:28)
at Modal (webpack-internal:///(app-client)/./components/Modal.tsx:15:11)
at div
at div
at div
at div
at Sidebar (webpack-internal:///(app-client)/./components/Sidebar.tsx:15:11)
at f (webpack-internal:///(app-client)/./node_modules/next-themes/dist/index.module.js:8:597)
at $ (webpack-internal:///(app-client)/./node_modules/next-themes/dist/index.module.js:8:348)
at Providers (webpack-internal:///(app-client)/./app/providers/Providers.tsx:7:11)
at body
at html
at RedirectErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/redirect-boundary.js:73:9)
at RedirectBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/redirect-boundary.js:81:11)
at NotFoundErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/not-found-boundary.js:51:9)
at NotFoundBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/not-found-boundary.js:59:11)
at ReactDevOverlay (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:66:9)
at HotReload (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:276:11)
at Router (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/app-router.js:90:11)
at ErrorBoundaryHandler (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/error-boundary.js:80:9)
at ErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/error-boundary.js:106:11)
at AppRouter (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/app-router.js:387:13)
at ServerRoot (webpack-internal:///(app-client)/./node_modules/next/dist/client/app-index.js:154:11)
at RSCComponent
at Root (webpack-internal:///(app-client)/./node_modules/next/dist/client/app-index.js:171:11)
I'm using this form inside the Dialog component which is being conditionally rendered like so:
"use client";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../components/ui/dialog";
import CreateReminder from "./CreateReminder";
import LoginForm from "./LoginForm";
import RegisterForm from "./RegisterForm";
import Subcategory from "./Subcategory";
import { Button } from "./ui/button";
import { IconType } from "react-icons";
const Modal: React.FC<{ label: string; icon: IconType }> = ({
label,
icon,
}) => {
return (
<Dialog>
<DialogTrigger className="w-full">
<Subcategory label={label} icon={icon} />
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-center py-2">{label}</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
{label === "Login" ? (
<LoginForm />
) : label === "Register" ? (
<RegisterForm />
) : (
<CreateReminder />
)}
</DialogContent>
</Dialog>
);
};
export default Modal;
Seems like you need to pass initial values when you define the form, because they are undefined at first, and then when you type inside the input its not undefined anymore.
When it changed from undefined
to 'some value'
the type will change from uncontrolled to controlled
const form = useForm<ReminderFormSchemaType>({
resolver: zodResolver(reminderFormSchema),
defaultValues: {
title: '',
description: '',
...
}
});