ReactT2 Dark_1200x303

Creating forms in React has become so easy now. We're going to learn how we can create custom forms using Hooks, and remove the necessity of downloading a ton of libraries for our applications. 

In this article, we're going to learn how we can use React Hooks to create custom forms in React. By creating our custom forms using Hooks, we can have low-level custom forms logic, which we can reuse in a lot of parts of our application without having to download a ton of libraries.

Forms are one of the core features of the web — a way we can let the user communicate with us and see what they’re thinking. Creating forms is not such an easy task. We need to keep track of every field, validate the value, empty when necessary, etc.

Every developer has their own way to build forms and their preferred libraries to do it with, but first we’ll go back to the time in React development and understand how we came from no library for forms in React, to a time where we have a lot of libraries and also we can create our own library, very easily.

Forms in React

Especially in React, forms aren’t a problem now like they were a few years ago. We started to research a lot, people started to create custom forms using patterns, such as HOCs and Render Props, and at some point a lot of libraries started to appear for form handling.

Eventually, the community started to adopt a library to handle and create forms: Redux Form, a library for form handling created by Erik Rasmussen. Redux was very popular at that time — and still is — and people started to use it in everything, including their forms. But Redux Form has a problem, or a deficit: it keeps the form state in the global state. We don’t need to keep our form state logic inside our global state because it’s just a form state. Also, Redux Form is not the best choice for very simple forms, and not even the best choice for high-level forms, those that require more logic to build.

Then, Jared Palmer created the library that is known today as the best library to create and handle forms in React: Formik. What are the benefits of Formik? Well, you have a very simple state form and can handle it very easily, without storing it in your global state. Formik works basically in three steps: first, it gets the values in and out of your form state, then you can validate it and test if everything's right, and last it handles your form submission.

If you have a pretty high-level form, which requires a lot of validation and logic inside of it, I highly recommend you go with Formik, but if you don't have a high-level form and are planning to use it with a simple form, I don't think it's the best choice for you. Formik is still the most-used library in React for forms handling, but sometimes there's no need to download a library and write a lot of code for a very simple form, and that's why we're going to learn how we can use Hooks to create custom forms and handle our form state logic very easily.

Custom Hooks

One of the best things that React Hooks brought to us was the ability to share code and state logic in any component that we want. We can easily create a custom hook and use it everywhere now, which makes for better state logic and streamlined, reused code.

I’ve seen a lot of people recently creating custom hooks for everything, even for those hooks they’re going to use in a single file. It’s not a best practice to create a new custom hook every time that you use Hooks — you just need to create a custom hook if you’ll use that same logic in more than one component.

Let's start to create our custom hook. First, we're going to create a new create-react-app :

create-react-app use

Now, inside our project, let's create a folder called hooks, but inside that folder, we're going to have one single file, which will be our custom hook.

We're going to create a new file inside that folder called useCustomForm, and inside that file put the following code:

    const useCustomForm = () => { 
      console.log('My first custom hook')
    }

    export default useCustomForm

Now, let's pass two arguments in our function: the first one is initialValues, which will be an object of our initial values of the form, and second a function called onSubmit, which will be the function that's going to submit our form values.

    const useCustomForm = ({ initialValues, onSubmit }) => { 
      console.log('My first custom hook')
    }

    export default useCustomForm

Now, let’s import some things and start to write our custom hook. First, we're going to import the useState hook to let us manage state in a function component, the useEffect hook to let us perform some side effects in our function, and the useRef hook to let us refer to something that mutates over time that we might want to persist.

    import { useState, useEffect, useRef } from "react";

We're going now start to create some state logic, inside our function put the following code:

    const [values, setValues] = useState(initialValues || {});
    const [errors, setErrors] = useState({});
    const [touched, setTouched] = useState({});
    const [onSubmitting, setOnSubmitting] = useState<boolean>(false);
    const [onBlur, setOnBlur] = useState<boolean>(false);

Next we're going to create a new const called formRendered using the useRef hook and use the useEffect hook, like this:

    const formRendered = useRef(true);

    useEffect(() => {
      if (!formRendered.current) {
        setValues(initialValues);
        setErrors({});
        setTouched({});
        setOnSubmitting(false);
        setOnBlur(false);
      }
      formRendered.current = false;
    }, [initialValues]);

Now, we're going to create our function. Since our custom hook is going to deal with forms, we're going to have three functions: handleChange to track the value of a field every time we change it, handleBlur to run blur events, and a handleSubmit function to submit our values when we hit the button. First, let's create our handleChange function:

    const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
      const { target } = event;
      const { name, value } = target;
      event.persist();
      setValues({ ...values, [name]: value });
    };

So, to explain what we're doing here: we're getting the event.target of an element, then we're setting the value of this element to be the value and the name to be the name. Pretty simple. Then we're setting it to our values state, but without removing the values of the old ones that we might have.

Now, we're going to create our handleBlur function, which is going to be like this:

    const handleBlur = (event: React.ChangeEvent<HTMLInputElement>) => {
      const { target } = event;
      const { name } = target;
      setTouched({ ...touched, [name]: true });
      setErrors({ ...errors });
    };

Pretty similar to the previous one, in this function, we're getting the name of an event.target and setting it to true in our touched state. If there are any errors, we're going to set it to our errors state.

In our last function now, which is going to be the handleSubmit, we're going to put the following code:

    const handleSubmit = (event: any) => {
      if (event) event.preventDefault();
      setErrors({ ...errors });
      onSubmit({ values, errors });
    };

In this function, we're simply setting the errors and then submitting the values and the errors using the onSubmit function, which we received as an argument. Our custom hook is done; all we need to do is return all the state and functions that we're going to need:

    return {
      values,
      errors,
      touched,
      handleChange,
      handleBlur,
      handleSubmit
    };

Our full custom hook will look like this:

    import { useState, useEffect, useRef } from "react";

    const useCustomForm = ({
      initialValues,
      onSubmit
    }) => {
      const [values, setValues] = useState(initialValues || {});
      const [errors, setErrors] = useState({});
      const [touched, setTouched] = useState({});
      const [onSubmitting, setOnSubmitting] = useState<boolean>(false);
      const [onBlur, setOnBlur] = useState<boolean>(false);

      const formRendered = useRef(true);

      useEffect(() => {
        if (!formRendered.current) {
          setValues(initialValues);
          setErrors({});
          setTouched({});
          setOnSubmitting(false);
          setOnBlur(false);
        }
        formRendered.current = false;
      }, [initialValues]);

      const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        const { target } = event;
        const { name, value } = target;
        event.persist();
        setValues({ ...values, [name]: value });
    };

      const handleBlur = (event: React.ChangeEvent<HTMLInputElement>) => {
        const { target } = event;
        const { name } = target;
        setTouched({ ...touched, [name]: true });
        setErrors({ ...errors });
    };

      const handleSubmit = (event: any) => {
        if (event) event.preventDefault();
        setErrors({ ...errors });
        onSubmit({ values, errors });
      };

      return {
        values,
        errors,
        touched,
        handleChange,
        handleBlur,
        handleSubmit
      };
    };

    export default useCustomForm;

Now that we created our custom hook, let's use it in our form and see how it'll work!

Using Hooks with Forms

Let's now use our custom hook inside our App.tsx file. First we're going to create our App component:

    import React from "react";

    const App = () => {
      return (
        <h1>App working!</h1>
      );
    };

    export default App;

Next, we're going to create a simple form and create three inputs: one for the name, another for the lastName, and another one for the age. So, our code will look like this:

    <form className="App">
      <h1>Custom Forms with Hooks</h1>

      <label>Name</label>
      <input
        type="text"
        name="name"
      />
      <br />

      <label>Lastname</label>
      <input
        type="text"
        name="lastName"
      />
      <br />

      <label>Age</label>
      <input
        type="number"
        name="age"
      />
      <br />

      <button type="submit">Submit</button>
    </form>

Now, we're starting to use our custom hook. First, let's import it in the top of our file:

    import useCustomForm from "../hooks/useCustomForm";

Inside our component, we're going to destructure some values and functions of our custom hook. To start to use it:

    const {
      values,
      errors,
      touched,
      handleChange,
      handleBlur,
      handleSubmit
    } = useCustomForm();

You'll need to create an object based on the type of fields that you’re going to have — in our case, we’ll need a name, lastName, and age. Now, let's create our initialValues object:

    const initialValues = {
      name: "",
      lastName: "",
      age: 0
    };

And now, we need to pass it as an argument to our useCustomForm hook:

    const {
      values,
      errors,
      touched,
      handleChange,
      handleBlur,
      handleSubmit
    } = useCustomForm({ initialValues });

We need to pass the other argument, an onSubmit function. So, let's create an onSubmit function passing it as an argument, and put some simple code:

    const {
      values,
      errors,
      touched,
      handleChange,
      handleBlur,
      handleSubmit
    } = useCustomForm({
      initialValues,
      onSubmit: values => console.log({ values })
    });

Now, all we need to do is to pass our handleSubmit function to our form element:

    <form onSubmit={handleSubmit} className="App">

After that, we need to pass a value for each input referencing the values that we imported from our custom hook, and then pass our handleChange function to each of our inputs, to track its values. For example, the name input will look like this:

    <input
      type=”text”
      name=”name”
      onChange={handleChange}
      value={values.name}
    />

And we need to repeat it to the other two inputs that we have. So, our App.tsx file will look like this:

    import React from "react";

    import useCustomForm from "../hooks/useCustomForm";

    const initialValues = {
      name: "",
      lastName: "",
      age: 0
    };

    const App = () => {
      const {
        values,
        errors,
        touched,
        handleChange,
        handleBlur,
        handleSubmit
      } = useCustomForm({
        initialValues,
        onSubmit: values => console.log({ values })
      });

      return (
        <form onSubmit={handleSubmit} className="App">
          <h1>Custom Forms with Hooks</h1>

          <label>Name</label>
          <input
            type="text"
            name="name"
            onChange={handleChange}
            value={values.name}
          />
          <br />

          <label>Lastname</label>
          <input
            type="text"
            name="lastName"
            onChange={handleChange}
            value={values.lastName}
          />
          <br />

          <label>Age</label>
          <input
            type="number"
            name="age"
            onChange={handleChange}
            value={values.age}
          />
          <br />
          <button type="submit">Submit</button>
        </form>
      );
    };

    export default App;

We now have our custom form hook working pretty well. If we type something on our inputs and then click Submit, we're going to see all of our values printed on the console.

Building Rich Forms with KendoReact

If you don’t want to create your forms from scratch, you can use a UI library, and I highly recommend you try out KendoReact, a complete UI component library for React, built with high-quality and responsive components.

It includes all the components that you need from a simple basic app until a complex app, so with KendoReact you can focus on what matters in your app and stop trying to build complex UI components.

Conclusion

In this article, we learned how we can use React Hooks to write better forms and state logic, removing the necessity of downloading a lot of libraries to handle forms such as Formik or Redux Form. Forms are not one of the easiest things to deal with in web development, but they're not too hard — we just complicate it sometimes, creating unnecessary logic and code. Now, after following this code, you can start to write your own custom form library, and start to use it in your projects.


Leonardo Maldonado
About the Author

Leonardo Maldonado

Leonardo is a Full Stack Developer, working with everything React-related, and loves to write about React and GraphQL to help developers. He also created the 33 JavaScript Concepts.

Related Posts

Comments

Comments are disabled in preview mode.