DEV Community

Cover image for How to create React Forms without libraries ✨
Fang Tanbamrung
Fang Tanbamrung

Posted on • Edited on • Originally published at Medium

How to create React Forms without libraries ✨

In web development, forms play a pivotal role. They serve as the interface for users to interact with your app or website. I have created a lot of React forms (way too many 🤣) with or without a UI library. Personally, for anything new, you should try to code or do a proof of concept without supporting libraries to understand how things work at the fundamental level. Initially, it may take a bit longer, but you will understand how things work better. Then, you can adapt to future requirements or challenges that may come your way.

In this article, I will share the key concepts with examples on how to create a proper React form. To create a form, you will need to consider the following points

  1. What is a form?
  2. Controlled vs Uncontrolled Inputs
  3. Building and Managing Form Input Fields
  4. Handling Form Submissions

Validex: The Most Complete Validation Collection for React Forms

Just a quick background about what I'm working on. Validex can help you validate React forms in minutes. It has over 36 validators ranging from string, password, credit card, email, file, image and more. Each validator has TypeScript and JavaScript codes, RegEx and Non-RegEx options and unit tests.

Validex - Complete Collection of Form Validations for React

I would be grateful if you could check Validex out. It will encourage me to make even more contents ❤️‍🔥


What is a form?

Imagine you're filling out a physical form at a doctor's office. It contains fields like your name, address, and reason for the visit. A form is like a digital version of that physical form, but on a website or an app. It allows users to interact with a website by letting you input information, like your email or a message, and then submit it. The submitted information can be used to perform various actions.

An example of an online form

For example, think of a contact form on a website. You can enter your name, email, and a message, and when you click "Submit," the website receives your information. This allows the website owner to respond to your message and get in touch with you.

Forms can be used for many different purposes, like signing up for a newsletter, leaving feedback, or placing an order online. The websites and apps use forms to interact with us and provide relevant services.


Uncontrolled vs Controlled Inputs

In React, form input fields can be divided into two categories: controlled and uncontrolled inputs. The main difference between them lies in how their values are managed and updated.

Uncontrolled Inputs

Uncontrolled inputs do not have direct data binding, and their current values are not managed by React. Instead, their values are only fetched or synced when needed, such as during the form submission or a specific event. This gives React less control over the input's value, and it relies on using ref or other methods to manually obtain the input value.

An example of uncontrolled input

Example:

import { useCallback, useRef } from "react";

export const Uncontrolled = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleClick = useCallback(() => {
    console.log(`Password: ${inputRef.current?.value}`);
  }, []);
  return (
    <>
      <div>
        <label htmlFor="password">Password: </label>
        <input type="password" id="password" ref={inputRef} />
        <button onClick={handleClick}>Submit</button>
      </div>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the example above, the input field is uncontrolled as it does not bind value with React state. Instead, it relies on the ref attribute to create a reference to the input. When the user clicks the submit button, the handleClick method accesses the input's value using the ref that bind to input to log the password.

Controlled Inputs

Controlled inputs allow React to control their current values through binding with a value property and subscribe it value's change with onChange event or onInput event

As a user interacts with the form, React keeps track of the input's state and updates the data accordingly.

An example of controlled input

Example:

import { ChangeEvent, useCallback, useState } from "react";

export const Controlled = () => {
  const [password, setPassword] = useState<string>("");

  const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
    setPassword(event.target.value);
  }, []);

  const handleSubmit = useCallback(() => {
    console.log("Password:", password);
  }, [password]);

  return (
    <>
      <div>
        <label htmlFor="password">Password: </label>
        <input
          type="password"
          onChange={handleChange}
          value={password}
          id="password"
        />
        <button onClick={handleSubmit}>Submit</button>
      </div>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the example above, the input field is controlled as its value is bound to the password value property. When the user types or updates the input, the password property value also updates accordingly.


Building and Managing Form Input Fields:

As you may know, there are many input types. In the interest of time, we will cover the more common ones.

Types of Input Fields

Input

This is one of the most common form elements. Let's create a text input field as an example.

An example of a text input

import { ChangeEvent, useCallback, useState } from "react";

export const Input = () => {
  const [value, setValue] = useState<string>("");

  const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  }, []);

  return (
    <>
      <div>
        <label htmlFor="name">Name: </label>
        <input type="text" id="name" value={value} onChange={handleChange} />
      </div>
      <div>value: {value}</div>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

Checkbox

Checkboxes allow users to select one or more options from a set.

An example of a checkbox

import { useCallback, useState } from "react";

export const Checkbox = () => {
  const [value, setValue] = useState(false);

  const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.checked);
  }, []);

  return (
    <>
      <div className="checkbox">
        <label htmlFor="checkbox"> Accept Terms: </label>
        <input
          type="checkbox"
          id="checkbox"
          checked={value}
          onChange={handleChange}
        />
      </div>
      <div>value: {JSON.stringify(value)}</div>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the above code, we bind the Accept Terms value to a checkbox input. When the checkbox is clicked, the Accept Terms value will automatically update to reflect the status of the checkbox.

Select

An example of a selection

Like Checkboxes Select allow users to select option from a set but in other UI

import { ChangeEvent, useCallback, useState } from "react";

export const Select = () => {
  const [value, setValue] = useState<string>("");

  const handleSelect = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
    setValue(event.target.value);
  }, []);

  return (
    <>
      <div>
        <label htmlFor="select">Select: </label>
        <select id="select" value={value} onChange={handleSelect}>
          <option disabled value="">
            Please choose one
          </option>
          <option value="volvo"> Volvo</option>
          <option value="saab">Saab</option>
          <option value="mercedes">Mercedes</option>
          <option value="audi">Audi</option>
        </select>
      </div>
      <div>selected: {value}</div>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the above code, we bind value of select element when user select option the change event will trigger and set React state

File

An example of a file upload

HTML doesn't support setting value to file inputs as this could pose a security risk. However, we can still capture the user-selected file(s) via event handling.

import { ChangeEvent, useCallback, useState } from "react";

export const FileInput = () => {
  const [_value, setValue] = useState<File>();

  const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
    const fileList = event.target.files;
    const newFile = fileList?.[0] ?? undefined;
    setValue(newFile);
    console.log(newFile);
  }, []);

  return (
    <>
      <div>
        <label htmlFor="file">Choose File:</label>
        <input type="file" id="file" onChange={handleChange} />
      </div>
    </>
  );
};

Enter fullscreen mode Exit fullscreen mode

In the example above, we listen to the input field’s change event. When a user selects a file, the handleChange function is called, where we can then process the selected file accordingly.


Handling Form Submissions

Handling form submissions in a React application typically involves capturing user input, validating the data, and handling the submitted data by performing an action, like making a request to an API, or updating the application's state. We will describe the steps and best practices for handling form submissions in a React.js application.

Steps for Handling Form Submissions in React

  1. Capture user input with value property and onChange event: The first step in handling form submissions is capturing user input using element prop value to force element to show what value it should be and onChange event to track what is user input to change current React state to show on element.

  2. Prevent default form submission behavior with "preventDefault function in submit event": By default, when a form is submitted, the page will refresh and the form data will be sent to the server. In React, we want to handle the form submission in client side by prevent default event when user submit <form> element.

  3. Create a method to handle form submission: Now that we have captured user input and prevented the default form submission behavior, we need to create a React method that will manage the form submission and perform necessary actions (e.g. validate user input, sending data to an API, updating the application state).

  4. Call the method to submit form: We will call the method to validate the form, make a call to the backend API and update local state.

Example: Handling Form Submissions

An example of a login form

import { ChangeEvent, useCallback, useState, useMemo } from "react";

function useInputValue() {
  const [value, setValue] = useState<string>("");
  const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  }, []);
  return [value, handleChange];
}

export const Login = () => {
  const [username, handleChangeUsername] = useInputValue();
  const [password, handleChangePassword] = useInputValue();
  const [remember, setRemember] = useState<boolean>(false);
  const handleChangeRemember = useCallback(
    (event: ChangeEvent<HTMLInputElement>) => {
      setRemember(event.target.checked);
    },
    []
  );

  const result = useMemo(() => {
    return JSON.stringify(
      {
        username,
        password,
        remember,
      },
      null,
      2
    );
  }, [username, password, remember]);

  const handleSubmit = useCallback<FormEventHandler>(
    (event) => {
      event.preventDefault();
      console.log(result);
    },
    [result]
  );

  return (
    <>
      <div className="form">
        <h2>Login</h2>
        <div>
          <form onSubmit={handleSubmit}>
            <div>
              <label htmlFor="username">Username</label>
            </div>
            <div>
              <input
                id="username"
                value={username}
                onChange={handleChangeUsername}
              />
            </div>
            <div>
              <label htmlFor="password">Password</label>
            </div>
            <div>
              <input
                id="password"
                value={password}
                onChange={handleChangePassword}
              />
            </div>
            <div className="checkbox">
              <input
                type="checkbox"
                checked={remember}
                onChange={handleChangeRemember}
              />
              Remember me
            </div>
            <button className="submit" type="submit">
              Submit
            </button>
          </form>
        </div>
      </div>
      <div>{result}</div>
    </>
  );
};

Enter fullscreen mode Exit fullscreen mode

For live demo and codes, please click on see demo.


What about form validation?

I intentionally not include form validation because I think it deserves a place of its own. Form validation is crucial to maintaining the integrity of your data and improving the user experience. I plan to explain how form validation works and different ways to validate forms in the next article.


Conclusion

By adhering to the concepts, methods, and tips provided in this guide, you can efficiently work with forms in React without the need for any external libraries. We encourage you to experiment further and keep enhancing your knowledge in React form development.

Once you are comfortable with the fundamental, it's perfectly normal to use a form library for your work. Although there are some learning curves, you can write cleaner codes and develop forms faster so you can focus on tackling more difficult tasks. If you are planning to use a form library, you may want to consider factors like feature limitations and maintainability. Here is an article by my colleague, 🔥 5 Best Libraries to Develop React.js Forms 2024 to get you started.


Validex: The Most Complete Validation Collection for React Forms

Validex - Complete Collection of Form Validations for React

If you feel like this article helped you, please check out Validex. It has over 36 validators (such as string, password, credit card, email, file, image and more) so that you can validate React forms in minutes.

It would encourage me to continue creating even more contents. Thank you in advance! 🙏

Top comments (2)

Collapse
 
brense profile image
Rense Bakker

You dont need to maintain references to each input like that when you use uncontrolled inputs, you can just get the input values with the FormData api in the form onChange and/or onSubmit handler:

const handleChange = useCallback((evt: FormEvent<HTMLFormElement>) => {
    const formData = new FormData(evt.currentTarget);
    const entries = Object.fromEntries(formData);
    console.log(entries);
  }, []);

return <form onChange={handleChange}>{...inputs...}</form>
Enter fullscreen mode Exit fullscreen mode

full example: stackblitz.com/edit/stackblitz-sta...

Collapse
 
fangtanbamrung profile image
Fang Tanbamrung

Hi Rense, yup, you make a good point. The reason I maintain references because it's based on a single input.