ApiaryActive
Try: pause · settings · learn · wipe
← Community / Reading Room
RF
react · 3 min read

react form state patterns

======================================================

======================================================

As a developer working with React, you've likely encountered forms at some point in your projects. Managing form state can be a tedious task, but don't worry, we're about to dive into some tried and tested patterns to make your life easier.

Uncontrolled vs Controlled Forms


Before we dive into the more complex patterns, let's cover the basics. In React, forms can be either uncontrolled or controlled.

Uncontrolled Forms

In an uncontrolled form, the state is not managed by React. The value of each input field is a reference to the DOM node itself, and changes are not reflected in the component's state.

import React from 'react';

function UncontrolledForm() {
  return (
    <form>
      <input type="text" />
      <button type="submit">Submit</button>
    </form>
  );
}

While uncontrolled forms work, they can lead to issues like losing form data on re-renders or when the component is remounted.

Controlled Forms

In a controlled form, the state is managed by React. The value attribute of each input field is bound to the component's state using the onChange event handler.

import React from 'react';

function ControlledForm() {
  const [name, setName] = React.useState('');

  return (
    <form>
      <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      <button type="submit">Submit</button>
    </form>
  );
}

Controlled forms are generally safer and easier to manage, but can become complex when dealing with multiple fields or validation.

useFormState


When working with controlled forms, it's common to use the useState hook to store the form data in state. However, this approach can lead to duplicated code and maintenance issues. That's where useFormState comes in – a hook that simplifies managing form state.

import { useFormState } from 'react-form-state';

function ControlledForm() {
  const [formState] = useFormState({
    name: '',
    email: ''
  });

  return (
    <form>
      <input type="text" name="name" value={formState.name} onChange={(e) => formState.setName(e.target.value)} />
      <input type="email" name="email" value={formState.email} onChange={(e) => formState.setEmail(e.target.value)} />
      <button type="submit">Submit</button>
    </form>
  );
}

useFormState encapsulates the state and provides methods for updating individual fields, making your code more concise and easier to maintain.

useOptimistic


Another common challenge with forms is handling optimistic updates – when a request is made before it's actually successful. useOptimistic is a hook that helps you manage these scenarios by providing a way to update the form state based on the server response.

import { useOptimistic } from 'react-form-state';

function ControlledForm() {
  const [formState] = useFormState({
    name: '',
    email: ''
  });
  const optimisticUpdate = useOptimistic();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await fetch('/api/form', {
        method: 'POST',
        body: JSON.stringify(formState)
      });

      if (!response.ok) {
        throw new Error('Failed to submit form');
      }

      optimisticUpdate(formState);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" value={formState.name} onChange={(e) => formState.setName(e.target.value)} />
      <input type="email" name="email" value={formState.email} onChange={(e) => formState.setEmail(e.target.value)} />
      <button type="submit">Submit</button>
    </form>
  );
}

By using useOptimistic, you can ensure that your form state is updated based on the server response, even if the request takes time to complete.

react-hook-form


When dealing with complex forms or multiple validation rules, it's often more convenient to use a dedicated library like react-hook-form. This library provides a robust API for managing form state and validation.

import { useForm } from 'react-hook-form';

function ControlledForm() {
  const { register, handleSubmit, errors } = useForm();

  const onSubmit = async (data) => {
    try {
      const response = await fetch('/api/form', {
        method: 'POST',
        body: JSON.stringify(data)
      });

      if (!response.ok) {
        throw new Error('Failed to submit form');
      }

      console.log('Form submitted successfully!');
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input type="text" {...register('name')} />
      {errors.name && <div>{errors.name.message}</div>}
      <input type="email" {...register('email')} />
      {errors.email && <div>{errors.email.message}</div>}
      <button type="submit">Submit</button>
    </form>
  );
}

By using react-hook-form, you can leverage its robust features for managing form state and validation.

When NOT to Use


While the patterns mentioned above are generally useful, there are cases where they might not be the best choice. For example:

  • If your form has a simple structure with only one or two fields, using useState directly might be sufficient.
  • When working with legacy code that relies on uncontrolled forms, it's often easier to stick with them rather than refactoring.

Related Apiary Lessons


If you're new to React or need more practice with form state management, check out the following lessons:

Bee-utifully Simple Code


By applying these patterns and choosing the right library for your needs, you'll be able to write clean, maintainable code that makes managing form state a breeze.

Frequently asked
What is react form state patterns about?
======================================================
What should you know about uncontrolled Forms?
In an uncontrolled form, the state is not managed by React. The value of each input field is a reference to the DOM node itself, and changes are not reflected in the component's state.
What should you know about controlled Forms?
In a controlled form, the state is managed by React. The value attribute of each input field is bound to the component's state using the onChange event handler.
References & sources
  1. Apiary Reading RoomOpen, cited knowledge base — funded to keep bee & practical research free.
From the Apiary Reading Room. Opinion & editorial — not financial advice. We don't overclaim.
More from the Reading Room