DEV Community

Cover image for How To Simplify React Forms State Handlers
Arjay
Arjay

Posted on • Originally published at arjayosma.com on

How To Simplify React Forms State Handlers

Recently posted this article about a React Design Pattern on my blog and wanted to share it here in Dev.to as well.


Web application forms are essential for data processing and display of information. We’ve had our fair share of using them for the reasons identified above. They’re an inevitable part of web development.

While learning React, you must’ve encountered constructs such as the example below from various tutorials. A piece of small code snippet that shows you how to use controlled components in render functions and how to assign them into different state variables.

Hook Example (> v16.8) - Functional Component

In this example, let’s assume we have three input fields that need to be stored inside our component’s state.

import React, { useState } from "react"

const MyForm = () => {
  // Form states
  const [firstName, setFirstName] = useState("")
  const [lastName, setLastName] = useState("")
  const [email, setEmail] = useState("")

  // Form handlers
  const handleFirstNameChange = event => {
    setFirstName(event.target.value)
  }
  const handleLastNameChange = event => {
    setLastName(event.target.value)
  }
  const handleEmailChange = event => {
    setEmail(event.target.value)
  }

  // Render components
  return (
    <>
      <input onChange={handleFirstNameChange} type="text" value={firstName} />
      <input onChange={handleLastNameChange} type="text" value={lastName} />
      <input onChange={handleEmailChange} type="text" value={email} />
    </>
  )
}

Notice that we have three different onChange handlers for each of our input fields. That’s a lot and will be a lot more if we add another input field.

Problem

Our example above will definitely work. However, when we encounter a requirement that will demand us to add another input field for our form, we will be forced to add another state handler.

This may pose as a threat to the maintainability and readability of our dear code.

Solution

Fortunately, we have a simple pattern that we can use for us to retain the maintainability and readability of our source codes.

We can create a custom React hook that we can use across our codebase repetitively without confusing our future selves. The solution will look something like the custom hook below.

/**
 * src/hooks/form.js
 */
import { useState } from "react"

export const useInputState = initialValue => {
  // Initialize state holder and value setter
  const [value, setValue] = useState(initialValue)
  // On value change handler
  const onChange = event => setValue(event.target.value)
  // Reset the current state
  const clear = () => setValue(initialValue)
  return [value, onChange, clear]
}

This custom hook works by handling the state value internally. It will return the value , change handler , and the setter of the state respectively, which will be used by the calling function.

Usage

To use the hook we recently created, see the example below that modifies our first example above.

import React from "react"
import { useInputState } from "hooks/form"

const MyForm = () => {
  // Form states and change handlers
  const [firstName, handleFirstNameChange] = useInputState("")
  const [lastName, handleLastNameChange] = useInputState("")
  const [email, handleEmailChange] = useInputState("")

  // Render components
  return (
    <>
      <input onChange={handleFirstNameChange} type="text" value={firstName} />
      <input onChange={handleLastNameChange} type="text" value={lastName} />
      <input onChange={handleEmailChange} type="text" value={email} />
    </>
  )
}

We minimized the number of lines in our original code by using the custom hook that we just created. We called on the useInputState hook and initialized it with the default value. Then we destructure the array returned by the hook with value data and the onChange function. In the modified code, we renamed the destructured values accordingly.

Different Flavors

This is not the only way to create and use this hook.

While updating my source codes, I did not use the hook above. Instead, I used a different approach wherein the returned value of the hook is not an array but an object. Check out the code below.

// Declaration
export const useInputState = initialValue => {
  const [value, setValue] = useState(initialValue)
  const onChange = event => setValue(event.target.value)
  return {
    value,
    setValue,
    attributes: {
      value,
      onChange,
    },
  }
}

/* ------------------------------------------------------ */

// Usage
const MyForm = () => {
  const { value: firstName, attributes } = useInputState("")
  // ...
  return (
    <>
      <input type="text" {...attributes} />
      {/* ... */}
    </>
  )
}

As usual, you can play around this setup and take into consideration how you’re going to use this on your end.

Benefits

These are the benefits of using this hook:

  1. Simple and clean code.
  2. Fewer lines to maintain.
  3. Readable code and easy to digest.

For sure, there’s a lot more than what was identified.

Conclusion

The introduction of React hooks allows you to always use functions instead of having to juggle the existing concepts of React such as functions, classes, and Higher-Order Components. This makes our codebases lean and simple to maintain.

We are also adding a new concept in our arsenal of Design Patterns that we can apply to our codebases, which can definitely make our development lives easier.

Before we go, always remember to stay awesome!

Top comments (2)

Collapse
 
griffinfoster profile image
Griffin Foster • Edited

Great post! Super informative and a great option for form state handling. If your goal is to improve readability and minimize code length, have you considered using a single state object with all fields stored within it. You would then use a single value change handler that would look like:

const onChange = (e) => setState({...state, [e.target.name]: e.target.value})

This function would eliminate the need for multiple lines of useState as well as a custom Hook, and instead cover all form fields in one line.
Just a thought, hope this is helpful and have a great day!

Collapse
 
arjayosma profile image
Arjay

This is awesome! I haven't looked much into this setup but this can be a great addition.
I'll experiment with this one and update this post and my blog if it works.

If I'm not mistaken, this is the hook equivalent to the implementation of pre-hook state handling of user input.

But the way I am seeing it is we're spreading the state and updating the entire state object, which I'm sure will definitely work. This can be a great addition to minimize and simplify codes.

I just wanted to point out, though I might have missed it, that the purpose of the examples I provided above was for extensibility and reusability in case we want to add functionality to the way we handle our forms. :D