Write your answer here
Loading editor...
Dev4Room
Loading editor...
Search for a command to run...
How to implement Tanstack Form with new shadcn Field component?
How to integrate Tanstack Query with oRPC?
What is the difference between ExpressJS and NextJS for backend development?
Why does pandas.concat in Python 3.13 result in MemoryError with small DataFrames when using axis=1?
Random question to test global search
You can create a field config then map the over the fields that use the same component
3+
1{profileFields.map((fieldConfig) => (
2 <Controller
3 key={fieldConfig.name}
4 name={fieldConfig.name}
5 control={form.control}
6 render=...
1{profileFields.map((fieldConfig) => (
2 <Controller
3 key={fieldConfig.name}
4 name={fieldConfig.name}
5 control={form.control}
6 render=...
Asked 1 month ago
1
292
I've read the docs about the implementation of Tanstack Form with shadcn Field component. But in their docs, all I saw was a bunch of boilerplate code. I want to implement a proper reusable form.
Here's there original template with Tanstack form:
1"use client"
2
3import * as React from "react"
4import { useForm } from "@tanstack/react-form"
5import { toast } from "sonner"
6import * as z from "zod"
7
8import { Button } from "@/components/ui/button"
9import {
10 Card,
11 CardContent,
12 CardDescription,
13 CardFooter,
14 CardHeader,
15 CardTitle,
16} from "@/components/ui/card"
17import {
18 Field,
19 FieldDescription,
20 FieldError,
21 FieldGroup,
22 FieldLabel,
23} from "@/components/ui/field"
24import { Input } from "@/components/ui/input"
25import {
26 InputGroup,
27 InputGroupAddon,
28 InputGroupText,
29 InputGroupTextarea,
30} from "@/components/ui/input-group"
31
32const formSchema = z.object({
33 title: z
34 .string()
35 .min(5, "Bug title must be at least 5 characters.")
36 .max(32, "Bug title must be at most 32 characters."),
37 description: z
38 .string()
39 .min(20, "Description must be at least 20 characters.")
40 .max(100, "Description must be at most 100 characters."),
41})
42
43export function BugReportForm() {
44 const form = useForm({
45 defaultValues: {
46 title: "",
47 description: "",
48 },
49 validators: {
50 onSubmit: formSchema,
51 },
52 onSubmit: async ({ value }) => {
53 toast("You submitted the following values:", {
54 description: (
55 <pre className="bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4">
56 <code>{JSON.stringify(value, null, 2)}</code>
57 </pre>
58 ),
59 position: "bottom-right",
60 classNames: {
61 content: "flex flex-col gap-2",
62 },
63 style: {
64 "--border-radius": "calc(var(--radius) + 4px)",
65 } as React.CSSProperties,
66 })
67 },
68 })
69
70 return (
71 <Card className="w-full sm:max-w-md">
72 <CardHeader>
73 <CardTitle>Bug Report</CardTitle>
74 <CardDescription>
75 Help us improve by reporting bugs you encounter.
76 </CardDescription>
77 </CardHeader>
78 <CardContent>
79 <form
80 id="bug-report-form"
81 onSubmit={(e) => {
82 e.preventDefault()
83 form.handleSubmit()
84 }}
85 >
86 <FieldGroup>
87 <form.Field
88 name="title"
89 children={(field) => {
90 const isInvalid =
91 field.state.meta.isTouched && !field.state.meta.isValid
92 return (
93 <Field data-invalid={isInvalid}>
94 <FieldLabel htmlFor={field.name}>Bug Title</FieldLabel>
95 <Input
96 id={field.name}
97 name={field.name}
98 value={field.state.value}
99 onBlur={field.handleBlur}
100 onChange={(e) => field.handleChange(e.target.value)}
101 aria-invalid={isInvalid}
102 placeholder="Login button not working on mobile"
103 autoComplete="off"
104 />
105 {isInvalid && (
106 <FieldError errors={field.state.meta.errors} />
107 )}
108 </Field>
109 )
110 }}
111 />
112 <form.Field
113 name="description"
114 children={(field) => {
115 const isInvalid =
116 field.state.meta.isTouched && !field.state.meta.isValid
117 return (
118 <Field data-invalid={isInvalid}>
119 <FieldLabel htmlFor={field.name}>Description</FieldLabel>
120 <InputGroup>
121 <InputGroupTextarea
122 id={field.name}
123 name={field.name}
124 value={field.state.value}
125 onBlur={field.handleBlur}
126 onChange={(e) => field.handleChange(e.target.value)}
127 placeholder="I'm having an issue with the login button on mobile."
128 rows={6}
129 className="min-h-24 resize-none"
130 aria-invalid={isInvalid}
131 />
132 <InputGroupAddon align="block-end">
133 <InputGroupText className="tabular-nums">
134 {field.state.value.length}/100 characters
135 </InputGroupText>
136 </InputGroupAddon>
137 </InputGroup>
138 <FieldDescription>
139 Include steps to reproduce, expected behavior, and what
140 actually happened.
141 </FieldDescription>
142 {isInvalid && (
143 <FieldError errors={field.state.meta.errors} />
144 )}
145 </Field>
146 )
147 }}
148 />
149 </FieldGroup>
150 </form>
151 </CardContent>
152 <CardFooter>
153 <Field orientation="horizontal">
154 <Button type="button" variant="outline" onClick={() => form.reset()}>
155 Reset
156 </Button>
157 <Button type="submit" form="bug-report-form">
158 Submit
159 </Button>
160 </Field>
161 </CardFooter>
162 </Card>
163 )
164}
165
1"use client"
2
3import * as React from "react"
4import { useForm } from "@tanstack/react-form"
5import { toast } from "sonner"
6import * as z from "zod"
7
8import { Button } from "@/components/ui/button"
9import {
10 Card,
11 CardContent,
12 CardDescription,
13 CardFooter,
14 CardHeader,
15 CardTitle,
16} from "@/components/ui/card"
17import {
18 Field,
19 FieldDescription,
20 FieldError,
21 FieldGroup,
22 FieldLabel,
23} from "@/components/ui/field"
24import { Input } from "@/components/ui/input"
25import {
26 InputGroup,
27 InputGroupAddon,
28 InputGroupText,
29 InputGroupTextarea,
30} from "@/components/ui/input-group"
31
32const formSchema = z.object({
33 title: z
34 .string()
35 .min(5, "Bug title must be at least 5 characters.")
36 .max(32, "Bug title must be at most 32 characters."),
37 description: z
38 .string()
39 .min(20, "Description must be at least 20 characters.")
40 .max(100, "Description must be at most 100 characters."),
41})
42
43export function BugReportForm() {
44 const form = useForm({
45 defaultValues: {
46 title: "",
47 description: "",
48 },
49 validators: {
50 onSubmit: formSchema,
51 },
52 onSubmit: async ({ value }) => {
53 toast("You submitted the following values:", {
54 description: (
55 <pre className="bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4">
56 <code>{JSON.stringify(value, null, 2)}</code>
57 </pre>
58 ),
59 position: "bottom-right",
60 classNames: {
61 content: "flex flex-col gap-2",
62 },
63 style: {
64 "--border-radius": "calc(var(--radius) + 4px)",
65 } as React.CSSProperties,
66 })
67 },
68 })
69
70 return (
71 <Card className="w-full sm:max-w-md">
72 <CardHeader>
73 <CardTitle>Bug Report</CardTitle>
74 <CardDescription>
75 Help us improve by reporting bugs you encounter.
76 </CardDescription>
77 </CardHeader>
78 <CardContent>
79 <form
80 id="bug-report-form"
81 onSubmit={(e) => {
82 e.preventDefault()
83 form.handleSubmit()
84 }}
85 >
86 <FieldGroup>
87 <form.Field
88 name="title"
89 children={(field) => {
90 const isInvalid =
91 field.state.meta.isTouched && !field.state.meta.isValid
92 return (
93 <Field data-invalid={isInvalid}>
94 <FieldLabel htmlFor={field.name}>Bug Title</FieldLabel>
95 <Input
96 id={field.name}
97 name={field.name}
98 value={field.state.value}
99 onBlur={field.handleBlur}
100 onChange={(e) => field.handleChange(e.target.value)}
101 aria-invalid={isInvalid}
102 placeholder="Login button not working on mobile"
103 autoComplete="off"
104 />
105 {isInvalid && (
106 <FieldError errors={field.state.meta.errors} />
107 )}
108 </Field>
109 )
110 }}
111 />
112 <form.Field
113 name="description"
114 children={(field) => {
115 const isInvalid =
116 field.state.meta.isTouched && !field.state.meta.isValid
117 return (
118 <Field data-invalid={isInvalid}>
119 <FieldLabel htmlFor={field.name}>Description</FieldLabel>
120 <InputGroup>
121 <InputGroupTextarea
122 id={field.name}
123 name={field.name}
124 value={field.state.value}
125 onBlur={field.handleBlur}
126 onChange={(e) => field.handleChange(e.target.value)}
127 placeholder="I'm having an issue with the login button on mobile."
128 rows={6}
129 className="min-h-24 resize-none"
130 aria-invalid={isInvalid}
131 />
132 <InputGroupAddon align="block-end">
133 <InputGroupText className="tabular-nums">
134 {field.state.value.length}/100 characters
135 </InputGroupText>
136 </InputGroupAddon>
137 </InputGroup>
138 <FieldDescription>
139 Include steps to reproduce, expected behavior, and what
140 actually happened.
141 </FieldDescription>
142 {isInvalid && (
143 <FieldError errors={field.state.meta.errors} />
144 )}
145 </Field>
146 )
147 }}
148 />
149 </FieldGroup>
150 </form>
151 </CardContent>
152 <CardFooter>
153 <Field orientation="horizontal">
154 <Button type="button" variant="outline" onClick={() => form.reset()}>
155 Reset
156 </Button>
157 <Button type="submit" form="bug-report-form">
158 Submit
159 </Button>
160 </Field>
161 </CardFooter>
162 </Card>
163 )
164}
165