loading...
Cover image for A React Hook You Don’t Need

A React Hook You Don’t Need

mmcshinsky profile image Michael McShinsky ・6 min read

Since the release of React Hooks in version 16.8.0, developers have found countless ways to use them to their advantage. These advantages come in the form of easily managed theme states to wrappers that makes dealing with the React lifecycle a little bit easier. Most of these are glorified utility functions that have been upgraded to work seemlessly in the React environment. They (these hooks) usually handle their scope of work closely with the component that is implementing their functionality.

I as well, made a hook… that no one needs. This hook is called useHandleChange. A hook that manages the state of your form and the functional logic of all input changes and clicks. This allows you to manage the diverse components and HTML elements you may want to use in your app. You also keep the freedom of implementing your own validators and callbacks after each change if you so desire.

Why do I think this is a hook you don’t need? Let’s take a look at how this hook came to be. In order to do so, we’ll have to address the first problem useHandleChange solved before hooks and functional React components were the new kid on the block.

Class Based Form Components

When handling form data in React, you have two choices. Controlled and uncontrolled components. Simply put, controlled components are HTML elements in which you directly control what the value of an element is and how to change it when a user interacts with it. Uncontrolled components are these same HTML (JSX) elements which have default values from React with no managed value, change, or click functionality. They act in their natural state without being managed or controlled by the React lifecycle or React event listeners. When using uncontrolled components, you typically grab their values from the DOM by hand and store them in your state or function variables upon form submission, rather than during every click of the mouse or clack of the keyboard.

Here is a basic example of controlled components in React.

    import React from 'react';

    export class SignUpForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          name: '',
          email: '',
        };
      }

    _handleChange = (event) => {
        this.setState({ [event.target.name]: event.target.value });
      };

    _handleSubmit = (event) => {
        event.preventDefault();
      };

    render() {
        return (
          <form onSubmit={this._handleSubmit}>
            <fieldset>
              <div>
                <label htmlFor='formName'>Name:</label>
                <input
                  id='formName'
                  name='name'
                  type='text'
                  value={this.state.name}
                  onChange={this._handleChange}
                />
              </div>
              <div>
                <label htmlFor='formEmail'>Email:</label>
                <input
                  id='formEmail'
                  name='email'
                  type='text'
                  value={this.state.email}
                  onChange={this._handleChange}
                />
              </div>
            </fieldset>
            <button type='submit'>Submit</button>
          </form>
        );
      }
    }

In this last example, we note that there is a state to manage the form’s inputs and functions that directly control the data manipulation of the inputs. These are then saved back to the React state until we need them for submission. We’re using bracket notation in the object to shortcut and avoid writing extra variables. This is seen in the setState function. Also note that we have one function, _handleChange. This function is managing all data flow between the inputs and the state.

    _handleChange = (event) => {
      this.setState({ [event.target.name]: event.target.value });
    };

Complicated Inputs

What were to happen if we started to add in multiple types of form inputs and elements? We’d have to start adding functions to manage their unique properties. HTML inputs allow for the following type attributes:

  • button

  • checkbox

  • color

  • date

  • datetime-local

  • email

  • file

  • hidden

  • image

  • month

  • number

  • password

  • radio

  • range

  • reset

  • search

  • submit

  • tel

  • time

  • url

  • week

Each of these type attribute values have a slight difference in how to handle their value, display the value, and handling the value in different UI states. Some may have to use the attribute onClick to capture data change instead of the attribute onChange. Luckily for us, most use the same convention for managing their Event object in React which are received as event.target.name and event.target.value. So by having our first example function, you’ve already covered most of your input types. The inputs in question that we will focus on right now are: text, checkbox, radio, and file. These are the most common types used in the majority of forms.

Here are some of the functions we would have to write just to manage these inputs.

    function _handleChange(event) {
      this.setState({ [event.target.name]: event.target.checked });
    }

    function _handleCheckboxChange(event) {
      this.setState({ [event.target.name]: event.target.checked });
    }

    function _handleRadioChange(event) {
      this.setState({ [event.target.name]: event.target.checked });
    }

    // single file
    function _handleFileChange(event) {
      let file = event.target.files[0];
      this.setState({ [event.target.name]: file });
    }

    // multiple files
    function _handleFileChange(event) {
      this.setState({ [event.target.name]: event.target.files });
    }

That is a lot of functions to handle your form data! If we have a lot of forms across our app, we start to bloat our app with unneeded code! We can refactor these into a single function to handle all value types dynamically.

    function _handleChange(event) {
      let name = event.target.name ? event.target.name : event.target.type;

      let value =
        e.target.type === 'checkbox' || e.target.type === 'radio'
          ? e.target.checked
          : e.target.value;

      if (event.target.type === 'file') {
        value = event.target.files[0];
      }

      this.setState({ [name]: value });
    }

We know have a function that updates state for a lot of scenarios. What if we updated this to be used as a utility instead of writing this in every React component in our app.

    export function handleChange(event, state, callback) {
      let obj = { ...state } || {};
      let name = event.target.name ? event.target.name : event.target.type;
      let value =
        e.target.type === 'checkbox' || e.target.type === 'radio'
          ? e.target.checked
          : e.target.value;

      if (event.target.type === 'file') {
        value = event.target.files[0];
      }

      obj[name] = value;

      if (callback && typeof callback === 'function') {
        callback(obj);
      } else {
        return obj;
      }
    }

    // Usage Example

    let newState = handleChange(event, state, (obj) => {
      //... OR - Do something with the new state object if you want to set state here instead of from the returned variable.
    });

    this.setState(newState);

Our functions have now been wrapped up into a nice utility function that you can import into every component and combine it with your state and event handling logic. Once React hooks came out, we did shortcut some of this by updating state right on the input element, but if you needed extra functionality like validation or chained reactions, sending your event object to a function like this still becomes valuable.

Born From The Ashes

Since, generally, the functions described above have become semi-obsolete for very simple state updates on forms, I found less of a need to write them out like we have above. As mentioned earlier, for extra validation and error catches, passing the event object to a controlling function still is useful for our needs. One (opinionated) issue with React hooks, generally, is the pattern of one value/function combination for every input value. This is one of the reasons the classic Class approach was so appealing for managing state as an object. While it can be done with object copying — state setting functions, or custom reducers. Hint: you could even put our utility function inside your reducer! These generally feel like extra bloat that make my code feel a little bit messy, but still effective.

Example of a React hook approach refactored from the first example:

    import React, { useState } from 'react';

    export default function SignUpForm() {
      const [name, setName] = useState('');
      const [email, setEmail] = useState('');

    _handleSubmit = (event) => {
        event.preventDefault();
      };

    return (
        <form onSubmit={_handleSubmit}>
          <fieldset>
            <div>
              <label for='formName'>Name:</label>
              <input
                id='formName'
                name='name'
                type='text'
                value={name}
                onChange={(e) => setName(e.target.value)}
              />
            </div>
            <div>
              <label for='formEmail'>Email:</label>
              <input
                id='formEmail'
                name='email'
                type='text'
                value={email}
                onChange={(e) => setEmail(e.target.value)}
              />
            </div>
          </fieldset>
          <button type='submit'>Submit</button>
        </form>
      );
    }

Now that we have React hooks and everything is a hook, what if I could take the functions created above and write a hook? I could add some more utilities like deep object key/value changes, constructor functions, and integrate state management onto the hook instead of the React components we are writing. What more, what if it is published to npm as a package that can be used in every project and catch all the newest updates and bug fixes that may come up without having to fix each project individually with handle written functions. Hence, the birth of useHandleChange, a npm package that serves a React hook to manage your form state using the situations above while still giving you the freedom of integrating the state change with your favorite framework of choice.

Conclusion

The reason I call it a React hook you don’t need is because most of the functionality can be captured by calling the new hooks setState functionality directly on the input element. If you find yourself with the same bloat code with minimal external requirements for managing event data against your state, this may be useful to you. I hope that you have found this exercise in form state change to be useful and beneficial in leveling up your code!


If you found this helpful or useful, please share a 💓, 🦄, or 🔖. Thanks!

Posted on Jun 29 by:

mmcshinsky profile

Michael McShinsky

@mmcshinsky

Father, Techie, and Fullstack Software Engineer with a passion for continuous learning and React of course! http://linkedin.com/in/michaelmcshinsky

Discussion

markdown guide