Different approaches to building a contact form in React
Topics
React
Published on
16 Jan 2025
1. Controlled Input Form (Basic Approach)
Best for: Beginners learning form fundamentals
Step-by-Step Guide
- Create state variables for each input
- Manual validation using simple conditionals
- Handle submission state with a loading indicator
1import { useState } from 'react';
2
3interface FormState {
4 name: string;
5 email: string;
6 message: string;
7}
8
9export default function ControlledForm() {
10 const [form, setForm] = useState<FormState>({
11 name: '',
12 email: '',
13 message: ''
14 });
15 const [errors, setErrors] = useState<Partial<FormState>>({});
16 const [isSubmitting, setIsSubmitting] = useState(false);
17
18 // Simple validation function
19 const validate = () => {
20 const newErrors: Partial<FormState> = {};
21 if (!form.name) newErrors.name = 'Name is required';
22 if (!form.email) newErrors.email = 'Email is required';
23 if (!form.message) newErrors.message = 'Message is required';
24 setErrors(newErrors);
25 return Object.keys(newErrors).length === 0;
26 };
27
28 const handleSubmit = (e: React.FormEvent) => {
29 e.preventDefault();
30 if (!validate()) return;
31
32 setIsSubmitting(true);
33 setTimeout(() => {
34 console.log('Submitted:', form);
35 setIsSubmitting(false);
36 }, 1000);
37 };
38
39 return (
40 <form onSubmit={handleSubmit}>
41 <div>
42 <label>Name:</label>
43 <input
44 value={form.name}
45 onChange={(e) => setForm({...form, name: e.target.value})}
46 disabled={isSubmitting}
47 />
48 {errors.name && <span style={{color: 'red'}}>{errors.name}</span>}
49 </div>
50
51 <div>
52 <label>Email:</label>
53 <input
54 type="email"
55 value={form.email}
56 onChange={(e) => setForm({...form, email: e.target.value})}
57 disabled={isSubmitting}
58 />
59 {errors.email && <span style={{color: 'red'}}>{errors.email}</span>}
60 </div>
61
62 <div>
63 <label>Message:</label>
64 <textarea
65 value={form.message}
66 onChange={(e) => setForm({...form, message: e.target.value})}
67 disabled={isSubmitting}
68 />
69 {errors.message && <span style={{color: 'red'}}>{errors.message}</span>}
70 </div>
71
72 <button type="submit" disabled={isSubmitting}>
73 {isSubmitting ? 'Sending...' : 'Send Message'}
74 </button>
75 </form>
76 );
77}
78
Why Use This?
- Complete control over form behavior
- Good for learning fundamentals
- No external dependencies
2. React Hook Form (Intermediate Approach)
Best for: Most real-world applications
Step-by-Step Guide
- Install dependencies:
npm install react-hook-form
- Use
useForm
hook for state management - Add inline validation rules
1import { useForm } from 'react-hook-form';
2
3type FormValues = {
4 name: string;
5 email: string;
6 message: string;
7};
8
9export default function RHFBasicForm() {
10 const {
11 register,
12 handleSubmit,
13 formState: { errors, isSubmitting }
14 } = useForm<FormValues>();
15
16 const onSubmit = async (data: FormValues) => {
17 await new Promise(resolve => setTimeout(resolve, 1000));
18 console.log('Submitted:', data);
19 };
20
21 return (
22 <form onSubmit={handleSubmit(onSubmit)}>
23 <div>
24 <label>Name:</label>
25 <input
26 {...register('name', { required: 'Name is required' })}
27 disabled={isSubmitting}
28 />
29 {errors.name && <span style={{color: 'red'}}>{errors.name.message}</span>}
30 </div>
31
32 <div>
33 <label>Email:</label>
34 <input
35 type="email"
36 {...register('email', {
37 required: 'Email is required',
38 pattern: {
39 value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
40 message: 'Invalid email address'
41 }
42 })}
43 disabled={isSubmitting}
44 />
45 {errors.email && <span style={{color: 'red'}}>{errors.email.message}</span>}
46 </div>
47
48 <div>
49 <label>Message:</label>
50 <textarea
51 {...register('message', {
52 required: 'Message is required',
53 minLength: {
54 value: 10,
55 message: 'Message must be at least 10 characters'
56 }
57 })}
58 disabled={isSubmitting}
59 />
60 {errors.message && <span style={{color: 'red'}}>{errors.message.message}</span>}
61 </div>
62
63 <button type="submit" disabled={isSubmitting}>
64 {isSubmitting ? 'Sending...' : 'Send Message'}
65 </button>
66 </form>
67 );
68}
69
Why Use This?
- Better performance (fewer re-renders)
- Less boilerplate code
- Built-in validation system
3. React Hook Form + Zod (Advanced Validation)
Best for: Complex validation needs
Step-by-Step Guide
- Install dependencies:
npm install zod @hookform/resolvers
- Create Zod validation schema
- Integrate with React Hook Form
1import { useForm } from 'react-hook-form';
2import { zodResolver } from '@hookform/resolvers/zod';
3import { z } from 'zod';
4
5const contactSchema = z.object({
6 name: z.string().min(1, 'Name is required'),
7 email: z.string().email('Invalid email address'),
8 message: z.string().min(10, 'Message must be at least 10 characters')
9});
10
11type FormValues = z.infer<typeof contactSchema>;
12
13export default function RHFZodForm() {
14 const {
15 register,
16 handleSubmit,
17 formState: { errors, isSubmitting }
18 } = useForm<FormValues>({
19 resolver: zodResolver(contactSchema)
20 });
21
22 const onSubmit = async (data: FormValues) => {
23 await new Promise(resolve => setTimeout(resolve, 1000));
24 console.log('Submitted:', data);
25 };
26
27 return (
28 <form onSubmit={handleSubmit(onSubmit)}>
29 <div>
30 <label>Name:</label>
31 <input {...register('name')} disabled={isSubmitting} />
32 {errors.name && <span style={{color: 'red'}}>{errors.name.message}</span>}
33 </div>
34
35 <div>
36 <label>Email:</label>
37 <input type="email" {...register('email')} disabled={isSubmitting} />
38 {errors.email && <span style={{color: 'red'}}>{errors.email.message}</span>}
39 </div>
40
41 <div>
42 <label>Message:</label>
43 <textarea {...register('message')} disabled={isSubmitting} />
44 {errors.message && <span style={{color: 'red'}}>{errors.message.message}</span>}
45 </div>
46
47 <button type="submit" disabled={isSubmitting}>
48 {isSubmitting ? 'Sending...' : 'Send Message'}
49 </button>
50 </form>
51 );
52}
53
Why Use This?
- Centralized validation rules
- Type-safe schema definitions
- Reusable validation logic
4. Shadcn UI + React Hook Form + Zod (Production-Ready)
Best for: Professional applications needing polished UI
Step-by-Step Guide
- Set up Shadcn:
npx shadcn-ui@latest init
- Install dependencies:
npm install @hookform/resolvers zod
- Create form components
1import { useForm } from 'react-hook-form';
2import { zodResolver } from '@hookform/resolvers/zod';
3import { z } from 'zod';
4import {
5 Form,
6 FormControl,
7 FormField,
8 FormItem,
9 FormLabel,
10 FormMessage,
11} from '@/components/ui/form';
12import { Input } from '@/components/ui/input';
13import { Textarea } from '@/components/ui/textarea';
14import { Button } from '@/components/ui/button';
15
16const contactSchema = z.object({
17 name: z.string().min(1, 'Name is required'),
18 email: z.string().email('Invalid email address'),
19 message: z.string().min(10, 'Message must be at least 10 characters')
20});
21
22type FormValues = z.infer<typeof contactSchema>;
23
24export default function ShadcnForm() {
25 const form = useForm<FormValues>({
26 resolver: zodResolver(contactSchema),
27 defaultValues: {
28 name: '',
29 email: '',
30 message: ''
31 }
32 });
33
34 const onSubmit = async (values: FormValues) => {
35 await new Promise(resolve => setTimeout(resolve, 1000));
36 console.log('Submitted:', values);
37 };
38
39 return (
40 <Form {...form}>
41 <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
42 <FormField
43 control={form.control}
44 name="name"
45 render={({ field }) => (
46 <FormItem>
47 <FormLabel>Name</FormLabel>
48 <FormControl>
49 <Input
50 placeholder="Your name"
51 {...field}
52 disabled={form.formState.isSubmitting}
53 />
54 </FormControl>
55 <FormMessage />
56 </FormItem>
57 )}
58 />
59
60 <FormField
61 control={form.control}
62 name="email"
63 render={({ field }) => (
64 <FormItem>
65 <FormLabel>Email</FormLabel>
66 <FormControl>
67 <Input
68 type="email"
69 placeholder="your@email.com"
70 {...field}
71 disabled={form.formState.isSubmitting}
72 />
73 </FormControl>
74 <FormMessage />
75 </FormItem>
76 )}
77 />
78
79 <FormField
80 control={form.control}
81 name="message"
82 render={({ field }) => (
83 <FormItem>
84 <FormLabel>Message</FormLabel>
85 <FormControl>
86 <Textarea
87 placeholder="Your message..."
88 {...field}
89 disabled={form.formState.isSubmitting}
90 />
91 </FormControl>
92 <FormMessage />
93 </FormItem>
94 )}
95 />
96
97 <Button
98 type="submit"
99 disabled={form.formState.isSubmitting}
100 >
101 {form.formState.isSubmitting ? 'Sending...' : 'Send Message'}
102 </Button>
103 </form>
104 </Form>
105 );
106}
107
Why Use This?
- Beautiful, accessible UI components
- Professional styling out of the box
- Combines best validation practices
Table of Contents