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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs