JAVASCRIPT
Create a Comprehensive `useForm` Hook for React
Learn to build a robust `useForm` hook in React for managing complex form states, including multiple inputs, dynamic validation, submission, and error handling, simplifying form development and improving user experience.
import { useState, useCallback } from 'react';
const useForm = (initialState, validate) => {
const [values, setValues] = useState(initialState);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = useCallback((event) => {
setValues(prevValues => ({
...prevValues,
[event.target.name]: event.target.value,
}));
}, []);
const handleSubmit = useCallback((callback) => (event) => {
if (event) event.preventDefault();
setIsSubmitting(true);
const validationErrors = validate(values);
setErrors(validationErrors);
if (Object.keys(validationErrors).length === 0) {
callback(values);
} else {
setIsSubmitting(false); // Stop submitting if there are errors
}
}, [values, validate]); // values and validate are dependencies for handleSubmit
const resetForm = useCallback(() => {
setValues(initialState);
setErrors({});
setIsSubmitting(false);
}, [initialState]); // initialState is a dependency for resetForm
return {
values,
errors,
isSubmitting,
handleChange,
handleSubmit,
resetForm,
};
};
export default useForm;
/* Example Usage (in a component):
import React from 'react';
import useForm from './useForm'; // Assuming useForm.js
const validateLoginForm = (values) => {
let 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, isSubmitting, handleChange, handleSubmit, resetForm } = useForm(
{ email: '', password: '' },
validateLoginForm
);
const loginUser = (formValues) => {
console.log('Submitting values:', formValues);
// Simulate API call
setTimeout(() => {
alert('Login successful! (Check console for values)');
// In a real app, you might want to reset form after successful API call
// or handle error state if API fails.
resetForm();
}, 1000);
};
return (
<form onSubmit={handleSubmit(loginUser)} style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '5px' }}>
<h2>Login Form</h2>
<div style={{ marginBottom: '10px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '5px' }}>Email:</label>
<input
id="email"
type="email"
name="email"
value={values.email}
onChange={handleChange}
style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }}
/>
{errors.email && <p style={{ color: 'red', fontSize: '0.9em', margin: '5px 0 0' }}>{errors.email}</p>}
</div>
<div style={{ marginBottom: '10px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '5px' }}>Password:</label>
<input
id="password"
type="password"
name="password"
value={values.password}
onChange={handleChange}
style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }}
/>
{errors.password && <p style={{ color: 'red', fontSize: '0.9em', margin: '5px 0 0' }}>{errors.password}</p>}
</div>
<button
type="submit"
disabled={isSubmitting}
style={{
padding: '10px 15px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginRight: '10px'
}}
>
{isSubmitting ? 'Submitting...' : 'Login'}
</button>
<button
type="button"
onClick={resetForm}
style={{
padding: '10px 15px',
backgroundColor: '#6c757d',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Reset
</button>
</form>
);
}
export default LoginForm;
*/
How it works: This custom `useForm` hook centralizes and simplifies the management of form state in React applications. It handles multiple input fields, tracks their values, applies a provided validation function, manages submission state, and allows for form resetting. By abstracting this common functionality, developers can create cleaner, more maintainable form components, separating presentation from complex form logic. The `useCallback` hook is used for `handleChange`, `handleSubmit`, and `resetForm` to prevent unnecessary re-creations of these functions, which is beneficial for performance, especially when passing them down to child components.