JAVASCRIPT
Build Dynamic Forms with Input Management and Validation using useForm
A custom React hook for handling multiple form input states and their validation, simplifying form management in complex applications.
import { useState, useCallback } from 'react';
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = useCallback((event) => {
event.persist();
setValues((prevValues) => ({
...prevValues,
[event.target.name]: event.target.value,
}));
// Clear error for the changed field
if (errors[event.target.name]) {
setErrors((prevErrors) => ({
...prevErrors,
[event.target.name]: undefined,
}));
}
}, [errors]); // Dependency on errors to clear them when field changes
const handleSubmit = useCallback(async (callback) => {
setIsSubmitting(true);
const newErrors = validate ? validate(values) : {};
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
await callback(values);
}
setIsSubmitting(false);
}, [values, validate]);
const resetForm = useCallback(() => {
setValues(initialValues);
setErrors({});
setIsSubmitting(false);
}, [initialValues]);
return {
values,
errors,
isSubmitting,
handleChange,
handleSubmit,
resetForm,
setValues, // Allow external modification if needed
setErrors, // Allow external modification if needed
};
}
export default useForm;
// Example Usage:
// import React from 'react';
// import useForm from './useForm';
//
// const validateLoginForm = (values) => {
// const errors = {};
// if (!values.email) {
// errors.email = 'Email is required';
// } else if (!/\S+@\S+\.\S+/.test(values.email)) {
// errors.email = 'Email address is invalid';
// }
// if (!values.password) {
// errors.password = 'Password is required';
// } else if (values.password.length < 6) {
// errors.password = 'Password needs to be 6 characters or more';
// }
// return errors;
// };
//
// function LoginForm() {
// const {
// values,
// errors,
// handleChange,
// handleSubmit,
// isSubmitting,
// resetForm
// } = useForm(
// { email: '', password: '' },
// validateLoginForm
// );
//
// const onSubmit = async (formValues) => {
// console.log('Submitting:', formValues);
// // Simulate API call
// await new Promise(resolve => setTimeout(resolve, 1000));
// alert('Login successful!');
// resetForm();
// };
//
// return (
// <form onSubmit={(e) => { e.preventDefault(); handleSubmit(onSubmit); }}>
// <div>
// <label>Email:</label>
// <input
// type="email"
// name="email"
// value={values.email}
// onChange={handleChange}
// />
// {errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}
// </div>
// <div>
// <label>Password:</label>
// <input
// type="password"
// name="password"
// value={values.password}
// onChange={handleChange}
// />
// {errors.password && <p style={{ color: 'red' }}>{errors.password}</p>}
// </div>
// <button type="submit" disabled={isSubmitting}>
// {isSubmitting ? 'Submitting...' : 'Login'}
// </button>
// <button type="button" onClick={resetForm}>Reset</button>
// </form>
// );
// }
// export default LoginForm;
How it works: The `useForm` hook provides a robust solution for managing form state, validation, and submission logic. It initializes form values and errors, offers a `handleChange` function for input updates, and a `handleSubmit` function that runs a provided validation callback before executing the submission logic. `useCallback` is used to memoize functions, preventing unnecessary re-renders, while `useState` manages the form data, validation errors, and submission status.