CMU Coding Bootcamp
1import { useState, useCallback } from "react"; // Import useState for managing state and useCallback for memoizing functions.
2
3export function useForm(initialValues = {}, validate = () => ({})) { // A custom hook for managing form state and validation. Takes initial form values and a validation function as arguments. If no validation function is provided, a default empty object is used, effectively skipping validation.
4 const [values, setValues] = useState(initialValues); // State for storing form field values. Initialized with initialValues.
5 const [errors, setErrors] = useState({}); // State for storing validation errors. Initialized as an empty object.
6 const [touched, setTouched] = useState({}); // State for tracking which fields have been touched/blurred. Initialized as an empty object. Not used in the current implementation
7 const [isSubmitting, setIsSubmitting] = useState(false); // State for tracking form submission status. Initialized to false.
8
9 // useCallback for memoizing the handleChange function
10 const handleChange = useCallback(
11 (e) => { // Function to handle changes in form fields. Takes an event object as an argument.
12 const { name, value } = e.target; // Extract name and value from the event target.
13 setValues((prev) => ({ ...prev, [name]: value })); // Update the values state with the new value, using name as the key.
14
15 // Clear the error for the field being modified (provides immediate feedback to the user)
16 if (errors[name]) { // If there's an existing error for this field
17 setErrors((prev) => ({ ...prev, [name]: "" })); // Clear the error for this field by setting it to an empty string.
18 }
19 },
20 [errors] // Dependency array: This function depends on the errors state.
21 );
22
23 // useCallback for memoizing the handleBlur function
24 const handleBlur = useCallback(
25 (e) => { // Function to handle blur events (when a field loses focus). Takes event object
26 const { name } = e.target; //get name from event target
27 setTouched((prev) => ({ ...prev, [name]: true })); // Update the touched state to mark the field as touched. Not used in current implementation
28
29 // Validate field on blur
30 const fieldErrors = validate({ [name]: values[name] }); // Validate only the blurred field using the provided validate function.
31 setErrors((prev) => ({ ...prev, ...fieldErrors })); // Update the errors state with any validation errors.
32 },
33 [values, validate] // Dependency array: This function depends on the values state and the validate function.
34 );
35
36 // useCallback for memoizing the handleSubmit function
37 const handleSubmit = useCallback(
38 async (onSubmit) => { // Function to handle form submission. Takes an onSubmit callback function as an argument.
39 setIsSubmitting(true); // Set isSubmitting to true to indicate form submission is in progress. Can be used to disable submit button, show loading indicator, etc.
40
41 // Validate all fields
42 const formErrors = validate(values); // Validate all form field values using the validate function.
43 setErrors(formErrors); // Update the errors state with all validation errors.
44
45 if (Object.keys(formErrors).length === 0) { // If there are no validation errors
46 try {
47 await onSubmit(values); // Call the provided onSubmit function with the form values. This assumes onSubmit is an async function.
48 setValues(initialValues); // Reset the form to its initial values after successful submission.
49 setTouched({}); //reset touched
50 } catch (error) { //catch any error during onSubmit
51 setErrors((prev) => ({ ...prev, submit: error.message })); // If an error occurs during submission, set a 'submit' error in the errors state.
52 }
53 }
54
55 setIsSubmitting(false); // Set isSubmitting back to false after submission is complete (successful or not).
56 },
57 [values, validate, initialValues] // Dependency array: This function depends on values, validate, and initialValues.
58 );
59
60 const reset = useCallback(() => { //function to reset form
61 setValues(initialValues); //reset values to initialValues
62 setErrors({}); //clear errors
63 setTouched({}); //clear touched fields
64 setIsSubmitting(false); //set isSubmitting to false
65 }, [initialValues]); // Dependency array: This function depends on initialValues.
66
67 // Validation helper for edge cases (this function performs additional validation)
68 const enhancedValidate = useCallback(
69 (fieldValues) => { //takes an object of field values as input
70 const newErrors = {}; //create empty object to store errors
71
72 for (const field in fieldValues) { //iterate over each field in fieldValues
73 const value = fieldValues[field]; //get the value of the field
74
75 // Generic validation for empty fields
76 if (!value?.trim()) { //if value is empty or contains only whitespace
77 newErrors[field] = `${field} is required.`; //set error message that field is required
78 continue; // Continue to the next field (important optimization: avoids unnecessary checks if a field is already invalid)
79 }
80
81 // Specific validation for email fields
82 if (
83 field === "email" && //if field is email and value is not a valid email
84 !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)
85 ) {
86 newErrors[field] = "Invalid email address."; //set error message for invalid email
87 continue; //continue to next field
88 }
89
90 // Add additional field-specific validations as needed Placeholder for adding more validation rules.
91 }
92
93 return newErrors; //return errors object
94 },
95 [] //empty dependency array since this function doesn't depend on any external values
96 );
97
98 return { //return an object containing form related data and functions
99 values, //form field values
100 errors, //validation errors
101 touched, //touched fields, not used in current implementation but can be used to conditionally display error messages after a field has been touched
102 isSubmitting, //submission status
103 handleChange, //function to handle field changes
104 handleBlur, //function to handle blur events
105 handleSubmit, //function to handle form submission
106 reset, //function to reset form
107 validate: enhancedValidate, //the enhanced validation function
108 };
109}