JAVASCRIPT
Simplify Form State & Validation with useForm
A robust React hook for managing complex form states, values, and errors, providing a streamlined API for handling user input and custom validation logic.
import { useState, useCallback, useMemo } from 'react';
const 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,
}));
if (errors[event.target.name]) {
setErrors(prevErrors => {
const newErrors = { ...prevErrors };
delete newErrors[event.target.name];
return newErrors;
});
}
}, [errors]);
const handleSubmit = useCallback(async (callback) => {
setIsSubmitting(true);
const validationErrors = await Promise.resolve(validate(values));
setErrors(validationErrors || {});
if (Object.keys(validationErrors || {}).length === 0) {
await Promise.resolve(callback(values));
}
setIsSubmitting(false);
}, [values, validate]);
const resetForm = useCallback(() => {
setValues(initialValues);
setErrors({});
setIsSubmitting(false);
}, [initialValues]);
const formData = useMemo(() => ({
values,
errors,
handleChange,
handleSubmit,
resetForm,
isSubmitting,
}), [values, errors, handleChange, handleSubmit, resetForm, isSubmitting]);
return formData;
};
export default useForm;
/*
// Example Usage:
const validateForm = (values) => {
const newErrors = {};
if (!values.username) {
newErrors.username = 'Username is required';
}
if (!values.email) {
newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(values.email)) {
newErrors.email = 'Email address is invalid';
}
return newErrors;
};
function LoginForm() {
const { values, errors, handleChange, handleSubmit, isSubmitting, resetForm } = useForm(
{ username: '', email: '', password: '' },
validateForm
);
const onSubmit = (formValues) => {
alert(JSON.stringify(formValues, null, 2));
console.log('Form submitted:', formValues);
return new Promise(resolve => setTimeout(resolve, 1000)); // Simulate API call
};
return (
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(onSubmit); }}>
<div>
<label>Username:</label>
<input
type="text"
name="username"
value={values.username || ''}
onChange={handleChange}
/>
{errors.username && <p style={{ color: 'red' }}>{errors.username}</p>}
</div>
<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}
/>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
<button type="button" onClick={resetForm}>Reset</button>
</form>
);
}
*/
How it works: The `useForm` hook simplifies managing complex form states, including input values, validation errors, and submission status. It takes `initialValues` and an optional `validate` function (which can be synchronous or asynchronous) that returns an errors object. `handleChange` updates input values and clears associated errors. `handleSubmit` runs the validation, updates error states, and only calls the provided callback if there are no errors, also managing a `isSubmitting` state. The `resetForm` function reverts the form to its initial state.