Forms and Input Handling

Build complex forms with validation and error handling in React.

Understanding Forms in React: From Basic to Advanced

Forms are everywhere on the web - from simple search boxes to complex registration forms. In React, we handle forms differently than in traditional HTML, giving us more control and better user experiences. Let's start from the very basics and build up to advanced form handling techniques.

Why Forms Matter Forms are how users interact with your application:

  • Login forms: How users access their accounts
  • Registration forms: How new users sign up
  • Contact forms: How users get in touch
  • Order forms: How users make purchases
  • Settings forms: How users customize their experience

The Traditional HTML Way vs The React Way In traditional HTML, forms work like this:

  1. User fills out the form
  2. User clicks submit
  3. Browser sends data to server
  4. Page refreshes with response

In React, we can do much better:

  1. User types → React updates immediately
  2. Instant validation as they type
  3. Dynamic form fields based on choices
  4. Submit without page refresh
  5. Better error handling and user feedback

Your First React Form: Starting Simple Before diving into controlled components, let's see the simplest possible form in React. This will help you understand why controlled components are useful.

1// 🎯 SIMPLE FORM EXAMPLE - Understanding the Basics
2
3import React from 'react';
4
5// ❌ NOT RECOMMENDED: Basic form without React state
6function SimpleHTMLForm() {
7 const handleSubmit = (e) => {
8 e.preventDefault(); // Prevent page refresh
9
10 // Access form data the old-fashioned way
11 const formData = new FormData(e.target);
12 const name = formData.get('name');
13 const email = formData.get('email');
14
15 console.log('Submitted:', { name, email });
16 };
17
18 return (
19 <form onSubmit={handleSubmit}>
20 <h3>Simple HTML Form (Not Recommended)</h3>
21
22 <div>
23 <label>Name:</label>
24 <input type="text" name="name" />
25 </div>
26
27 <div>
28 <label>Email:</label>
29 <input type="email" name="email" />
30 </div>
31
32 <button type="submit">Submit</button>
33 </form>
34 );
35}
36
37// ✅ RECOMMENDED: Your first controlled form
38function MyFirstControlledForm() {
39 // Step 1: Create state for each input
40 const [name, setName] = useState('');
41 const [email, setEmail] = useState('');
42
43 // Step 2: Handle input changes
44 const handleNameChange = (e) => {
45 setName(e.target.value);
46 };
47
48 const handleEmailChange = (e) => {
49 setEmail(e.target.value);
50 };
51
52 // Step 3: Handle form submission
53 const handleSubmit = (e) => {
54 e.preventDefault();
55 console.log('Submitted:', { name, email });
56
57 // Clear form after submission
58 setName('');
59 setEmail('');
60 };
61
62 return (
63 <form onSubmit={handleSubmit}>
64 <h3>My First Controlled Form</h3>
65
66 <div>
67 <label>
68 Name:
69 <input
70 type="text"
71 value={name} // Controlled by React state
72 onChange={handleNameChange} // Update state on change
73 />
74 </label>
75 <p>You typed: {name}</p> {/* See the value in real-time! */}
76 </div>
77
78 <div>
79 <label>
80 Email:
81 <input
82 type="email"
83 value={email}
84 onChange={handleEmailChange}
85 />
86 </label>
87 <p>Your email: {email}</p>
88 </div>
89
90 <button type="submit">Submit</button>
91
92 {/* Show current form state */}
93 <div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f0f0f0' }}>
94 <h4>Current Form State:</h4>
95 <pre>{JSON.stringify({ name, email }, null, 2)}</pre>
96 </div>
97 </form>
98 );
99}

Controlled Components: The Foundation of React Forms

Controlled components are one of the most important concepts in React form handling. Think of them as form inputs that are "controlled" by React state - React becomes the single source of truth for what the user sees and types. This might seem like extra work at first, but it gives you superpowers when building interactive forms!

What Are Controlled Components? In traditional HTML, form elements like <input>, <textarea>, and <select> maintain their own state. When a user types into an input field, the browser manages what's displayed. With controlled components, React takes over this responsibility. The current value of the input is stored in React state, and any changes go through React first.

Real-World Analogy Imagine you're a teacher writing on a whiteboard. In traditional HTML forms, it's like students writing directly on the board themselves - you can see what they wrote, but you don't control it. With controlled components, it's like students telling you what to write, and you write it on the board. You have complete control over what appears, how it's formatted, and whether to accept or reject their input.

Why Use Controlled Components?

  1. Single Source of Truth: All form data lives in one place (React state), making it easy to access and manage
  2. Input Validation: You can validate user input in real-time as they type
  3. Input Formatting: Automatically format phone numbers, credit cards, or dates as users type
  4. Conditional Logic: Show/hide fields or change options based on other inputs
  5. Form Persistence: Easily save form state to localStorage or restore previous values
  6. Debugging: See exactly what's in your form at any time through React DevTools

The Two-Way Data Binding Pattern Controlled components implement a pattern called "two-way data binding":

  1. The component's state determines what's displayed in the input (state → UI)
  2. User interactions update the state, which then updates the display (UI → state → UI)

Common Input Types and How to Control Them:

  • Text Inputs: Control with value prop
  • Checkboxes: Control with checked prop
  • Radio Buttons: Control with checked prop for each option
  • Select Dropdowns: Control with value prop on the select element
  • Textareas: Control with value prop

Benefits Over Uncontrolled Components:

  • Instant input validation
  • Dynamic form fields
  • Easier testing
  • Better integration with React ecosystem
  • Predictable behavior
1// 🌟 CONTROLLED COMPONENTS EXAMPLE
2// Demonstrating basic form input types and patterns
3
4import React, { useState } from 'react';
5
6function BasicControlledForm() {
7 // Single source of truth for all form data
8 const [formData, setFormData] = useState({
9 username: '',
10 email: '',
11 age: '',
12 bio: '',
13 country: '',
14 newsletter: false,
15 experience: 'beginner',
16 interests: []
17 });
18
19 // Generic handler for most inputs
20 const handleInputChange = (e) => {
21 const { name, value, type, checked } = e.target;
22
23 setFormData(prev => ({
24 ...prev,
25 [name]: type === 'checkbox' ? checked : value
26 }));
27 };
28
29 // Special handler for multi-select checkboxes
30 const handleInterestChange = (interest) => {
31 setFormData(prev => ({
32 ...prev,
33 interests: prev.interests.includes(interest)
34 ? prev.interests.filter(i => i !== interest)
35 : [...prev.interests, interest]
36 }));
37 };
38
39 const handleSubmit = (e) => {
40 e.preventDefault();
41 console.log('Form submitted:', formData);
42 };
43
44 return (
45 <form onSubmit={handleSubmit}>
46 {/* Text Input */}
47 <div>
48 <label>
49 Username:
50 <input
51 type="text"
52 name="username"
53 value={formData.username}
54 onChange={handleInputChange}
55 />
56 </label>
57 </div>
58
59 {/* Email Input */}
60 <div>
61 <label>
62 Email:
63 <input
64 type="email"
65 name="email"
66 value={formData.email}
67 onChange={handleInputChange}
68 />
69 </label>
70 </div>
71
72 {/* Number Input */}
73 <div>
74 <label>
75 Age:
76 <input
77 type="number"
78 name="age"
79 value={formData.age}
80 onChange={handleInputChange}
81 min="1"
82 max="120"
83 />
84 </label>
85 </div>
86
87 {/* Textarea */}
88 <div>
89 <label>
90 Bio:
91 <textarea
92 name="bio"
93 value={formData.bio}
94 onChange={handleInputChange}
95 rows="4"
96 />
97 </label>
98 </div>
99
100 {/* Select Dropdown */}
101 <div>
102 <label>
103 Country:
104 <select
105 name="country"
106 value={formData.country}
107 onChange={handleInputChange}
108 >
109 <option value="">-- Select --</option>
110 <option value="us">United States</option>
111 <option value="uk">United Kingdom</option>
112 <option value="ca">Canada</option>
113 </select>
114 </label>
115 </div>
116
117 {/* Checkbox */}
118 <div>
119 <label>
120 <input
121 type="checkbox"
122 name="newsletter"
123 checked={formData.newsletter}
124 onChange={handleInputChange}
125 />
126 Subscribe to newsletter
127 </label>
128 </div>
129
130 {/* Radio Buttons */}
131 <fieldset>
132 <legend>Experience Level:</legend>
133 {['beginner', 'intermediate', 'advanced'].map(level => (
134 <label key={level}>
135 <input
136 type="radio"
137 name="experience"
138 value={level}
139 checked={formData.experience === level}
140 onChange={handleInputChange}
141 />
142 {level.charAt(0).toUpperCase() + level.slice(1)}
143 </label>
144 ))}
145 </fieldset>
146
147 {/* Multiple Checkboxes */}
148 <fieldset>
149 <legend>Interests:</legend>
150 {['React', 'Vue', 'Angular'].map(tech => (
151 <label key={tech}>
152 <input
153 type="checkbox"
154 checked={formData.interests.includes(tech)}
155 onChange={() => handleInterestChange(tech)}
156 />
157 {tech}
158 </label>
159 ))}
160 </fieldset>
161
162 <button type="submit">Submit</button>
163
164 {/* Display current form state */}
165 <div style={{ marginTop: '20px' }}>
166 <h4>Form State:</h4>
167 <pre>{JSON.stringify(formData, null, 2)}</pre>
168 </div>
169 </form>
170 );
171}
172
173// Additional examples showing input formatting
174function FormattedInputExample() {
175 const [phone, setPhone] = useState('');
176
177 // Format phone number as (123) 456-7890
178 const formatPhoneNumber = (value) => {
179 const phoneNumber = value.replace(/D/g, '');
180 if (phoneNumber.length < 4) return phoneNumber;
181 if (phoneNumber.length < 7) {
182 return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3)}`;
183 }
184 return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3, 6)}-${phoneNumber.slice(6, 10)}`;
185 };
186
187 return (
188 <div>
189 <label>
190 Phone Number:
191 <input
192 type="tel"
193 value={phone}
194 onChange={(e) => setPhone(formatPhoneNumber(e.target.value))}
195 placeholder="(123) 456-7890"
196 />
197 </label>
198 </div>
199 );
200}
201
202// Dynamic form fields example
203function DynamicFormFields() {
204 const [participants, setParticipants] = useState([
205 { id: 1, name: '', email: '' }
206 ]);
207
208 const addParticipant = () => {
209 const newId = participants.length + 1;
210 setParticipants([...participants, { id: newId, name: '', email: '' }]);
211 };
212
213 const removeParticipant = (id) => {
214 setParticipants(participants.filter(p => p.id !== id));
215 };
216
217 const updateParticipant = (id, field, value) => {
218 setParticipants(participants.map(p =>
219 p.id === id ? { ...p, [field]: value } : p
220 ));
221 };
222
223 return (
224 <div>
225 {participants.map((participant, index) => (
226 <div key={participant.id}>
227 <h4>Participant {index + 1}</h4>
228 <input
229 type="text"
230 placeholder="Name"
231 value={participant.name}
232 onChange={(e) => updateParticipant(participant.id, 'name', e.target.value)}
233 />
234 <input
235 type="email"
236 placeholder="Email"
237 value={participant.email}
238 onChange={(e) => updateParticipant(participant.id, 'email', e.target.value)}
239 />
240 {participants.length > 1 && (
241 <button onClick={() => removeParticipant(participant.id)}>
242 Remove
243 </button>
244 )}
245 </div>
246 ))}
247 <button onClick={addParticipant}>Add Participant</button>
248 </div>
249 );
250}
251
252// 💡 KEY TAKEAWAYS:
253// 1. Controlled components give you full control over form inputs
254// 2. React state is the single source of truth
255// 3. You can format, validate, and transform input in real-time
256// 4. Dynamic and conditional forms are easy to implement

Form Validation: Ensuring Data Quality

Form validation is crucial for ensuring users provide correct and complete information. React gives us the flexibility to implement validation in multiple ways, from simple required fields to complex business rules. Let's explore how to build robust form validation that provides great user experience.

Why Validation Matters Good validation:

  • Prevents errors: Catches mistakes before they reach your server
  • Saves time: Users fix issues immediately instead of after submission
  • Improves UX: Clear feedback helps users succeed
  • Reduces server load: Invalid data never gets sent
  • Ensures data quality: Your database stays clean and consistent

Types of Validation

  1. Field-level validation: Check individual fields as users type
  2. Form-level validation: Check the entire form before submission
  3. Async validation: Check with server (username availability, etc.)
  4. Cross-field validation: Validate fields that depend on each other

When to Validate

  • On change: Immediate feedback as users type
  • On blur: When users leave a field
  • On submit: Final check before sending data
  • Debounced: After user stops typing for a moment

Validation Best Practices

  • Show errors clearly but not aggressively
  • Provide helpful error messages
  • Indicate required fields
  • Show success states too
  • Don't validate empty optional fields
  • Consider accessibility (screen readers)
1// 🎯 FORM VALIDATION EXAMPLE
2
3import React, { useState, useEffect } from 'react';
4
5function ValidationExample() {
6 const [formData, setFormData] = useState({
7 email: '',
8 password: '',
9 confirmPassword: ''
10 });
11
12 const [errors, setErrors] = useState({});
13 const [touched, setTouched] = useState({});
14
15 // Validation logic
16 const validateField = (name, value) => {
17 switch (name) {
18 case 'email':
19 if (!value) return 'Email is required';
20 if (!/S+@S+.S+/.test(value)) return 'Email is invalid';
21 return '';
22
23 case 'password':
24 if (!value) return 'Password is required';
25 if (value.length < 8) return 'Password must be at least 8 characters';
26 if (!/[A-Z]/.test(value)) return 'Password must contain uppercase';
27 if (!/[a-z]/.test(value)) return 'Password must contain lowercase';
28 if (!/[0-9]/.test(value)) return 'Password must contain a number';
29 return '';
30
31 case 'confirmPassword':
32 if (!value) return 'Please confirm your password';
33 if (value !== formData.password) return 'Passwords do not match';
34 return '';
35
36 default:
37 return '';
38 }
39 };
40
41 const handleChange = (e) => {
42 const { name, value } = e.target;
43 setFormData(prev => ({ ...prev, [name]: value }));
44
45 // Validate if field has been touched
46 if (touched[name]) {
47 setErrors(prev => ({ ...prev, [name]: validateField(name, value) }));
48 }
49 };
50
51 const handleBlur = (e) => {
52 const { name, value } = e.target;
53 setTouched(prev => ({ ...prev, [name]: true }));
54 setErrors(prev => ({ ...prev, [name]: validateField(name, value) }));
55 };
56
57 const handleSubmit = (e) => {
58 e.preventDefault();
59
60 // Validate all fields
61 const newErrors = {};
62 Object.keys(formData).forEach(key => {
63 const error = validateField(key, formData[key]);
64 if (error) newErrors[key] = error;
65 });
66
67 setErrors(newErrors);
68 setTouched({ email: true, password: true, confirmPassword: true });
69
70 if (Object.keys(newErrors).length === 0) {
71 console.log('Form is valid! Submitting...', formData);
72 }
73 };
74
75 return (
76 <form onSubmit={handleSubmit}>
77 <div>
78 <label>
79 Email:
80 <input
81 type="email"
82 name="email"
83 value={formData.email}
84 onChange={handleChange}
85 onBlur={handleBlur}
86 style={{ borderColor: errors.email && touched.email ? 'red' : '' }}
87 />
88 </label>
89 {errors.email && touched.email && (
90 <span style={{ color: 'red', fontSize: '14px' }}>{errors.email}</span>
91 )}
92 </div>
93
94 <div>
95 <label>
96 Password:
97 <input
98 type="password"
99 name="password"
100 value={formData.password}
101 onChange={handleChange}
102 onBlur={handleBlur}
103 style={{ borderColor: errors.password && touched.password ? 'red' : '' }}
104 />
105 </label>
106 {errors.password && touched.password && (
107 <span style={{ color: 'red', fontSize: '14px' }}>{errors.password}</span>
108 )}
109 </div>
110
111 <div>
112 <label>
113 Confirm Password:
114 <input
115 type="password"
116 name="confirmPassword"
117 value={formData.confirmPassword}
118 onChange={handleChange}
119 onBlur={handleBlur}
120 style={{ borderColor: errors.confirmPassword && touched.confirmPassword ? 'red' : '' }}
121 />
122 </label>
123 {errors.confirmPassword && touched.confirmPassword && (
124 <span style={{ color: 'red', fontSize: '14px' }}>{errors.confirmPassword}</span>
125 )}
126 </div>
127
128 <button type="submit">Submit</button>
129 </form>
130 );
131}
132
133// Async validation example
134function AsyncValidation() {
135 const [username, setUsername] = useState('');
136 const [isChecking, setIsChecking] = useState(false);
137 const [isAvailable, setIsAvailable] = useState(null);
138
139 // Debounced username check
140 useEffect(() => {
141 if (!username || username.length < 3) {
142 setIsAvailable(null);
143 return;
144 }
145
146 const timeoutId = setTimeout(async () => {
147 setIsChecking(true);
148
149 // Simulate API call
150 await new Promise(resolve => setTimeout(resolve, 1000));
151
152 // Check if username is taken
153 const taken = ['admin', 'user', 'test'].includes(username.toLowerCase());
154 setIsAvailable(!taken);
155 setIsChecking(false);
156 }, 500);
157
158 return () => clearTimeout(timeoutId);
159 }, [username]);
160
161 return (
162 <div>
163 <label>
164 Username:
165 <input
166 type="text"
167 value={username}
168 onChange={(e) => setUsername(e.target.value)}
169 placeholder="Check availability"
170 />
171 {isChecking && <span> Checking...</span>}
172 {!isChecking && isAvailable === true && <span style={{ color: 'green' }}> ✓ Available</span>}
173 {!isChecking && isAvailable === false && <span style={{ color: 'red' }}> ✗ Taken</span>}
174 </label>
175 </div>
176 );
177}
178
179// 💡 KEY VALIDATION TAKEAWAYS:
180// 1. Validate on blur for better UX
181// 2. Show errors only after user interaction (touched)
182// 3. Validate all fields on submit
183// 4. Use clear, helpful error messages
184// 5. Consider async validation for unique fields

Custom Form Hooks: Reusable Form Logic

As you build more forms, you'll notice patterns emerging. Custom hooks allow you to extract and reuse form logic across different components. This makes your code more maintainable and your forms more consistent.

What Are Custom Hooks? Custom hooks are JavaScript functions that:

  • Start with "use" (like useForm, useValidation)
  • Can call other hooks
  • Return values and functions for your components to use
  • Encapsulate complex logic in a reusable way

Benefits of Custom Form Hooks

  1. Code Reusability: Write validation logic once, use it everywhere
  2. Consistency: All forms behave the same way
  3. Separation of Concerns: UI components stay focused on rendering
  4. Easier Testing: Test form logic independently
  5. Better Organization: Keep form logic in one place

Common Custom Hook Patterns

  • useForm: Manages form state and validation
  • useField: Manages individual field state
  • useValidation: Handles validation rules
  • useFormSubmit: Manages submission state and API calls
  • useDebounce: Delays validation or API calls

When to Create Custom Hooks Create a custom hook when you:

  • Repeat the same logic in multiple components
  • Have complex state management logic
  • Want to share stateful logic between components
  • Need to organize complex component logic
1// 🎯 CUSTOM FORM HOOKS EXAMPLE
2
3import React, { useState, useEffect, useCallback } from 'react';
4
5// Custom hook for form management
6function useForm(initialValues, validate) {
7 const [values, setValues] = useState(initialValues);
8 const [errors, setErrors] = useState({});
9 const [touched, setTouched] = useState({});
10 const [isSubmitting, setIsSubmitting] = useState(false);
11
12 // Handle input changes
13 const handleChange = useCallback((e) => {
14 const { name, value, type, checked } = e.target;
15 const fieldValue = type === 'checkbox' ? checked : value;
16
17 setValues(prev => ({
18 ...prev,
19 [name]: fieldValue
20 }));
21
22 // Validate on change if field was touched
23 if (touched[name] && validate) {
24 const validationErrors = validate({ ...values, [name]: fieldValue });
25 setErrors(prev => ({
26 ...prev,
27 [name]: validationErrors[name]
28 }));
29 }
30 }, [values, touched, validate]);
31
32 // Handle blur events
33 const handleBlur = useCallback((e) => {
34 const { name } = e.target;
35 setTouched(prev => ({ ...prev, [name]: true }));
36
37 if (validate) {
38 const validationErrors = validate(values);
39 setErrors(prev => ({
40 ...prev,
41 [name]: validationErrors[name]
42 }));
43 }
44 }, [values, validate]);
45
46 // Handle form submission
47 const handleSubmit = useCallback((onSubmit) => async (e) => {
48 e.preventDefault();
49 setIsSubmitting(true);
50
51 // Touch all fields
52 const touchedAll = {};
53 Object.keys(values).forEach(key => {
54 touchedAll[key] = true;
55 });
56 setTouched(touchedAll);
57
58 // Validate all fields
59 const validationErrors = validate ? validate(values) : {};
60 setErrors(validationErrors);
61
62 // Submit if no errors
63 if (Object.keys(validationErrors).length === 0) {
64 await onSubmit(values);
65 }
66
67 setIsSubmitting(false);
68 }, [values, validate]);
69
70 // Reset form
71 const resetForm = useCallback(() => {
72 setValues(initialValues);
73 setErrors({});
74 setTouched({});
75 setIsSubmitting(false);
76 }, [initialValues]);
77
78 return {
79 values,
80 errors,
81 touched,
82 isSubmitting,
83 handleChange,
84 handleBlur,
85 handleSubmit,
86 resetForm
87 };
88}
89
90// Example: Contact form using custom hook
91function ContactForm() {
92 // Validation function
93 const validate = (values) => {
94 const errors = {};
95
96 if (!values.name) {
97 errors.name = 'Name is required';
98 } else if (values.name.length < 2) {
99 errors.name = 'Name must be at least 2 characters';
100 }
101
102 if (!values.email) {
103 errors.email = 'Email is required';
104 } else if (!/S+@S+.S+/.test(values.email)) {
105 errors.email = 'Email is invalid';
106 }
107
108 if (!values.message) {
109 errors.message = 'Message is required';
110 } else if (values.message.length < 10) {
111 errors.message = 'Message must be at least 10 characters';
112 }
113
114 return errors;
115 };
116
117 // Use the custom hook
118 const {
119 values,
120 errors,
121 touched,
122 isSubmitting,
123 handleChange,
124 handleBlur,
125 handleSubmit,
126 resetForm
127 } = useForm(
128 { name: '', email: '', message: '' },
129 validate
130 );
131
132 // Submit handler
133 const onSubmit = async (formValues) => {
134 console.log('Submitting:', formValues);
135 // Simulate API call
136 await new Promise(resolve => setTimeout(resolve, 1000));
137 alert('Form submitted successfully!');
138 resetForm();
139 };
140
141 return (
142 <form onSubmit={handleSubmit(onSubmit)}>
143 <div>
144 <input
145 type="text"
146 name="name"
147 placeholder="Your Name"
148 value={values.name}
149 onChange={handleChange}
150 onBlur={handleBlur}
151 />
152 {errors.name && touched.name && (
153 <span style={{ color: 'red' }}>{errors.name}</span>
154 )}
155 </div>
156
157 <div>
158 <input
159 type="email"
160 name="email"
161 placeholder="Your Email"
162 value={values.email}
163 onChange={handleChange}
164 onBlur={handleBlur}
165 />
166 {errors.email && touched.email && (
167 <span style={{ color: 'red' }}>{errors.email}</span>
168 )}
169 </div>
170
171 <div>
172 <textarea
173 name="message"
174 placeholder="Your Message"
175 value={values.message}
176 onChange={handleChange}
177 onBlur={handleBlur}
178 rows="4"
179 />
180 {errors.message && touched.message && (
181 <span style={{ color: 'red' }}>{errors.message}</span>
182 )}
183 </div>
184
185 <button type="submit" disabled={isSubmitting}>
186 {isSubmitting ? 'Sending...' : 'Send Message'}
187 </button>
188 <button type="button" onClick={resetForm}>
189 Reset
190 </button>
191 </form>
192 );
193}
194
195// Custom hook for debouncing
196function useDebounce(value, delay) {
197 const [debouncedValue, setDebouncedValue] = useState(value);
198
199 useEffect(() => {
200 const handler = setTimeout(() => {
201 setDebouncedValue(value);
202 }, delay);
203
204 return () => clearTimeout(handler);
205 }, [value, delay]);
206
207 return debouncedValue;
208}
209
210// 💡 CUSTOM HOOK BEST PRACTICES:
211// 1. Always prefix custom hooks with "use"
212// 2. Keep hooks focused on a single responsibility
213// 3. Return an object for easier destructuring
214// 4. Make hooks reusable across components
215// 5. Handle cleanup in useEffect hooks