React Forms | Hooks / Fetch / Rest / MySQL / PHP *

+ REACT

RESOURCES

php react rest api crudUsing Hooks + Fetchvalidate formsphp react rest api crudForm Data

https://www.digitalocean.com/community/tutorials/how-to-build-forms-in-react

How To Build Forms in React

DevelopmentJavaScriptReact

The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

Introduction

Forms are a crucial component of React web applications. They allow users to directly input and submit data in components ranging from a login screen to a checkout page. Since most React applications are single page applications (SPAs), or web applications that load a single page through which new data is displayed dynamically, you won’t submit the information directly from the form to a server. Instead, you’ll capture the form information on the client-side and send or display it using additional JavaScript code.

React forms present a unique challenge because you can either allow the browser to handle most of the form elements and collect data through React change events, or you can use React to fully control the element by setting and updating the input value directly. The first approach is called an uncontrolled component because React is not setting the value. The second approach is called a controlled component because React is actively updating the input.

In this tutorial, you’ll build forms using React and handle form submissions with an example app that submits requests to buy apples. You’ll also learn the advantages and disadvantages of controlled and uncontrolled components. Finally, you’ll dynamically set form properties to enable and disable fields depending on the form state. By the end of this tutorial, you’ll be able to make a variety of forms using text inputs, checkboxes, select lists, and more.

Prerequisites

Step 1 — Creating a Basic Form with JSX

In this step, you’ll create an empty form with a single element and a submit button using JSX. You’ll handle the form submit event and pass the data to another service. By the end of this step, you’ll have a basic form that will submit data to an asynchronous function.

To begin, open App.js:

nano src/components/App/App.js

You are going to build a form for purchasing apples. Create a [`](https://www.digitalocean.com/community/tutorial_series/how-to-build-a-website-with-html#how-to-use-a-,-the-html-content-division-element) with aclassNameof. Then add anh3tag with the text “How About Them Apples” and an emptyform` element by adding the following highlighted code:

form-tutorial/src/components/App/App.js

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      <form>
      </form>
    </div>
  )
}

export default App;

Next, inside the <form> tag, add a <fieldset> element with an <input> element surrounded by a <label> tag. By wrapping the <input> element with a <label> tag, you are aiding screen readers by associating the label with the input. This will increase the accessibility of your application.

Finally, add a submit <button> at the bottom of the form:

form-tutorial/src/components/App/App.js

import React from 'react';
import './App.css';

function App() {
  return(
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      <form>
      <fieldset>
         <label>
           <p>Name</p>
           <input name="name" />
         </label>
       </fieldset>
       <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Save and close the file. Then open App.css to set the styling:

nano src/components/App/App.css

Add padding to the .wrapper and margin to the fieldset to give some space between elements:

form-tutorial/src/components/App/App.css

.wrapper {
    padding: 5px 20px;
}

.wrapper fieldset {
    margin: 20px 0;
}

Save and close the file. When you do, the browser will reload and you’ll see a basic form.

Basic form with a field for "name" and a submit button

If you click on the Submit button, the page will reload. Since you are building a single page application, you will prevent this standard behavior for a button with a type="submit". Instead, you’ll handle the submit event inside the component.

Open App.js:

nano src/components/App/App.js

To handle the event, you’ll add an event handler to the <form> element, not the <button>. Create a function called handleSubmit that will take the SyntheticEvent as an argument. TheSyntheticEvent is a wrapper around the standard Event object and contains the same interface. Call .preventDefault to stop the page from submitting the form then trigger an alert to show that the form was submitted:

form-tutorial/src/components/App/App.js

import React from 'react';
import './App.css';

function App() {
  const handleSubmit = event => {
   event.preventDefault();
   alert('You have submitted the form.')
 }

  return(
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      <form onSubmit={handleSubmit}>
        <fieldset>
          <label>
            <p>Name</p>
            <input name="name" />
          </label>
        </fieldset>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Save the file. When you do the browser will reload. If you click the submit button, the alert will pop up, but the window will not reload.

Form submit alert

In many React applications, you’ll send the data to an external service, like a Web API. When the service resolves, you’ll often show a success message, redirect the user, or do both.

To simulate an API, add a setTimeout function in the handleSubmit function. This will create an asynchronous operation that waits a certain amount of time before completing, which behaves similarly to a request for external data. Then use the useState Hook to create a submitting variable and a setSubmitting function. Call setSubmitting(true) when the data is submitted and call setSubmitting(false) when the timeout is resolved:

form-tutorial/src/components/App/App.js

import React, { useState } from 'react';
import './App.css';

function App() {
  const [submitting, setSubmitting] = useState(false);
  const handleSubmit = event => {
    event.preventDefault();
   setSubmitting(true);

   setTimeout(() => {
     setSubmitting(false);
   }, 3000)
 }

  return(
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      {submitting &&
       <div>Submtting Form...</div>
     }
      <form onSubmit={handleSubmit}>
        <fieldset>
          <label>
            <p>Name</p>
            <input name="name" />
          </label>
        </fieldset>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

In addition, you will alert the user that their form is submitting by displaying a short message in the HTML that will display when submitting is true.

Save the file. When you do, the browser will reload and you’ll receive the message on submit:

Form submitting shows message for 3 seconds

Now you have a basic form that handles the submit event inside the React component. You’ve connected it to your JSX using the onSubmit event handler and you are using Hooks to conditionally display an alert while the handleSubmit event is running.

In the next step, you’ll add more user inputs and save the data to state as the user fills out the form.

Step 2 — Collecting Form Data Using Uncontrolled Components

In this step, you’ll collect form data using uncontrolled components. An uncontrolled component is a component that does not have a value set by React. Instead of setting the data on the component, you’ll connect to the onChange event to collect the user input. As you build the components, you’ll learn how React handles different input types and how to create a reusable function to collect form data into a single object.

By the end of this step, you’ll be able to build a form using different form elements, including dropdowns and checkboxes. You’ll also be able to collect, submit, and display form data.

Note: In most cases, you’ll use controlled components for your React application. But it’s a good idea to start with uncontrolled components so that you can avoid subtle bugs or accidental loops that you might introduce when incorrectly setting a value.

Currently, you have a form that can submit information, but there is nothing to submit. The form has a single <input> element, but you are not collecting or storing the data anywhere in the component. In order to be able to store and process the data when the user submits a form, you’ll need to create a way to manage state. You’ll then need to connect to each input using an event handler.

Inside App.js, use the useReducer Hook to create a formData object and a setFormData function. For the reducer function, pull the name and value from the event.target object and update the state by spreading the current state while adding the name and value at the end. This will create a state object that preserves the current state while overwriting specific values as they change:

form-tutorial/src/components/App/App.js

import React, { useReducer, useState } from 'react';
import './App.css';

const formReducer = (state, event) => {
 return {
   ...state,
   [event.target.name]: event.target.value
 }
}

function App() {
  const [formData, setFormData] = useReducer(formReducer, {});
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = event => {
    event.preventDefault();
    setSubmitting(true);

    setTimeout(() => {
      setSubmitting(false);
    }, 3000)
  }

  return(
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      {submitting &&
        <div>Submtting Form...</div>
      }
      <form onSubmit={handleSubmit}>
        <fieldset>
          <label>
            <p>Name</p>
            <input name="name" onChange={setFormData}/>
          </label>
        </fieldset>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

After making the reducer, add setFormData to the onChange event handler on the input. Save the file. When you do, the browser will reload. However, if you try and type in the input, you’ll get an error:

Error with SyntheticEvent

The problem is that the SyntheticEvent is reused and cannot be passed to an asynchronous function. In other words, you can’t pass the event directly. To fix this, you’ll need to pull out the data you need before calling the reducer function.

Update the reducer function to take an object with a property of name and value. Then create a function called handleChange that pulls the data from the event.target and passes the object to setFormData. Finally, update the onChange event handler to use the new function:

form-tutorial/src/components/App/App.js

import React, { useReducer, useState } from 'react';
import './App.css';

const formReducer = (state, event) => {<^>
 return {
   ...state,
   [event.name]: event.value
 }
}

function App() {
  const [formData, setFormData] = useReducer(formReducer, {});
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = event => {
    event.preventDefault();
    setSubmitting(true);

    setTimeout(() => {
      setSubmitting(false);
    }, 3000);
  }

  const handleChange = event => {
    setFormData({
      name: event.target.name,
      value: event.target.value,
    });
  }

  return(
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      {submitting &&
        <div>Submtting Form...</div>
      }
      <form onSubmit={handleSubmit}>
        <fieldset>
          <label>
            <p>Name</p>
            <input name="name" onChange={handleChange}/>
          </label>
        </fieldset>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Save the file. When you do the page will refresh and you’ll be able to enter data.

Now that you are collecting the form state, update the user display message to show the data in an unordered list (<ul>) element.

Convert the data to an array using Object.entries, then map over the data converting each member of the array to an <li> element with the name and the value. Be sure to use the name as the key prop for the element:

form-tutorial/src/components/App/App.js

...
  return(
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      {submitting &&
       <div>
         You are submitting the following:
         <ul>
           {Object.entries(formData).map(([name, value]) => (
             <li key={name}><strong>{name}</strong>:{value.toString()}</li>
           ))}
         </ul>
       </div>
      }
      <form onSubmit={handleSubmit}>
        <fieldset>
          <label>
            <p>Name</p>
            <input name="name" onChange={handleChange}/>
          </label>
        </fieldset>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Save the file. When you do the page will reload and you’ll be able to enter and submit data:

Fill out the form and submit

Now that you have a basic form, you can add more elements. Create another <fieldset> element and add in a <select> element with different apple varieties for each <option>, an <input> with a type="number" and a step="1" to get a count that increments by 1, and an <input> with a type="checkbox" for a gift wrapping option.

For each element, add the handleChange function to the onChange event handler:

form-tutorial/src/components/App/App.js

...
  return(
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      {submitting &&
        <div>
          You are submitting the following:
          <ul>
            {Object.entries(formData).map(([name, value]) => (
              <li key={name}><strong>{name}</strong>: {value.toString()}</li>
            ))}
          </ul>
        </div>
      }
      <form onSubmit={handleSubmit}>
        <fieldset>
          <label>
            <p>Name</p>
            <input name="name" onChange={handleChange}/>
          </label>
        </fieldset>
        <fieldset>
         <label>
           <p>Apples</p>
           <select name="apple" onChange={handleChange}>
               <option value="">--Please choose an option--</option>
               <option value="fuji">Fuji</option>
               <option value="jonathan">Jonathan</option>
               <option value="honey-crisp">Honey Crisp</option>
           </select>
         </label>
         <label>
           <p>Count</p>
           <input type="number" name="count" onChange={handleChange} step="1"/>
         </label>
         <label>
           <p>Gift Wrap</p>
           <input type="checkbox" name="gift-wrap" onChange={handleChange} />
         </label>
       </fieldset>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Save the file. When you do, the page will reload and you’ll have a variety of input types for your form:

Form with all input types

There is one special case here to take into consideration. The value for the gift wrapping checkbox will always be "on", regardless of whether the item is checked or not. Instead of using the event’s value, you’ll need to use the checked property.

Update the handleChange function to see if the event.target.type is checkbox. If it is, pass the event.target.checked property as the value instead of event.target.value:

form-tutorial/src/components/App/App.js

import React, { useReducer, useState } from 'react';
import './App.css';

...

function App() {
  const [formData, setFormData] = useReducer(formReducer, {});
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = event => {
    event.preventDefault();
    setSubmitting(true);

    setTimeout(() => {
      setSubmitting(false);
    }, 3000);
  }

  const handleChange = event => {
   const isCheckbox = event.target.type === 'checkbox';
   setFormData({
     name: event.target.name,
     value: isCheckbox ? event.target.checked : event.target.value,
   })
 }
...

In this code, you use the ? ternary operator to make the conditional statement.

Save the file. After the browser refreshes, fill out the form and click submit. You’ll find that the alert matches the data in the form:

Form elements submitting correct data

In this step, you learned how to create uncontrolled form components. You saved the form data to a state using the useReducer Hook and reused that data in different components. You also added different types of form components and adjusted your function to save the correct data depending on the element type.

In the next step, you’ll convert the components to controlled components by dynamically setting the component value.

Step 3 — Updating Form Data Using Controlled Components

In this step, you’ll dynamically set and update data using controlled components. You’ll add a value prop to each component to set or update the form data. You’ll also reset the form data on submit.

By the end of this step, you’ll be able to dynamically control form data using React state and props.

With uncontrolled components, you don’t have to worry about synchronizing data. Your application will always hold on to the most recent changes. But there are many situations where you’ll need to both read from and write to an input component. To do this, you’ll need the component’s value to be dynamic.

In the previous step, you submitted a form. But after the form submission was successful, the form still contained the old stale data. To erase the data from each input, you’ll need to change the components from uncontrolled components to controlled components.

A controlled component is similar to an uncontrolled component, but React updates the value prop. The downside is that if you are not careful and do not properly update the value prop the component will appear broken and won’t seem to update.

In this form, you are already storing the data, so to convert the components, you’ll update the value prop with data from the formData state. There is one problem, though: the value cannot be undefined. If your value is undefined, you’ll receive an error in your console.

Since your initial state is an empty object, you’ll need to set the value to be either the value from formData or a default value such as an empty string. For example, the value for the name would be formData.name || '':

form-tutorial/src/components/App/App.js

...
  return(
    <div className="wrapper">
      <h3>How About Them Apples</h3>
      {submitting &&
        <div>
          You are submitting the following:
          <ul>
            {Object.entries(formData).map(([name, value]) => (
              <li key={name}><strong>{name}</strong>: {value.toString()}</li>
            ))}
          </ul>
        </div>
      }
      <form onSubmit={handleSubmit}>
        <fieldset>
          <label>
            <p>Name</p>
            <input name="name" onChange={handleChange} value={formData.name || ''}/>
          </label>
        </fieldset>
        <fieldset>
          <label>
            <p>Apples</p>
            <select name="apple" onChange={handleChange} value={formData.apple || ''}>
                <option value="">--Please choose an option--</option>
                <option value="fuji">Fuji</option>
                <option value="jonathan">Jonathan</option>
                <option value="honey-crisp">Honey Crisp</option>
            </select>
          </label>
          <label>
            <p>Count</p>
            <input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
          </label>
          <label>
            <p>Gift Wrap</p>
            <input type="checkbox" name="gift-wrap" onChange={handleChange} checked={formData['gift-wrap'] || false}/>
          </label>
        </fieldset>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

As before, the checkbox is a little different. Instead of setting a value, you’ll need to set the checked attribute. If the attribute is truthy, the browser will show the box as checked. Set the initial checked attribute to false with formData['gift-wrap'] || false.

If you want to pre-fill the form, add some default data to the formData state. Set a default value for the count by giving formState a default value of { count: 100 }. You could also set the default values in the initial object, but you’d need to filter out the falsy values before displaying the form information:

form-tutorial/src/components/App/App.js

...

function App() {
  const [formData, setFormData] = useReducer(formReducer, {
   count: 100,
 });
  const [submitting, setSubmitting] = useState(false);
...

Save the file. When you do, the browser will reload and you’ll see the input with the default data:

Form with default count

Note: The value attribute is different from the placeholder attribute, which is native on browsers. The placeholder attribute shows information but will disappear as soon as the user makes a change; it is not stored on the component. You can actively edit the value, but a placeholder is just a guide for users.

Now that you have active components, you can clear the data on submit. To do so, add a new condition in your formReducer. If event.reset is truthy, return an object with empty values for each form element. Be sure to add a value for each input. If you return an empty object or an incomplete object, the components will not update since the value is undefined.

After you add the new event condition in the formReducer, update your submit function to reset the state when the function resolves:

form-tutorial/src/components/App/App.js

import React, { useReducer, useState } from 'react';
import './App.css';

const formReducer = (state, event) => {
  if(event.reset) {
   return {
     apple: '',
     count: 0,
     name: '',
     'gift-wrap': false,
   }
 }
  return {
    ...state,
    [event.name]: event.value
  }
}

function App() {
  const [formData, setFormData] = useReducer(formReducer, {
    count: 100
  });
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = event => {
    event.preventDefault();
    setSubmitting(true);

    setTimeout(() => {
      setSubmitting(false);
      setFormData({
       reset: true
     })
    }, 3000);
  }

...

Save the file. When you do, the browser will reload and the form will clear on submit.

Save the form and then clear the data

In this step, you converted your uncontrolled components to controlled components by setting the value or the checked attributes dynamically. You also learned how to refill data by setting a default state and how to clear the data by updating the form reducer to return default values.

In this next step, you’ll set form component properties dynamically and disable a form while it is submitting.

Step 4 — Dynamically Updating Form Properties

In this step, you’ll dynamically update form element properties. You’ll set properties based on previous choices and disable your form during submit to prevent accidental multiple submissions.

Currently, each component is static. They do not change as the form changes. In most applications, forms are dynamic. Fields will change based on the previous data. They’ll validate and show errors. They may disappear or expand as you fill in other components.

Like most React components, you can dynamically set properties and attributes on components and they will re-render as the data changes.

Try setting an input to be disabled until a condition is met by another input. Update the gift wrapping checkbox to be disabled unless the user selects the fuji option.

Inside App.js, add the disabled attribute to the checkbox. Make the property truthy if the formData.apple is fuji:

form-tutorial/src/components/App/App.js

...
        <fieldset>
          <label>
            <p>Apples</p>
            <select name="apple" onChange={handleChange} value={formData.apple || ''}>
                <option value="">--Please choose an option--</option>
                <option value="fuji">Fuji</option>
                <option value="jonathan">Jonathan</option>
                <option value="honey-crisp">Honey Crisp</option>
            </select>
          </label>
          <label>
            <p>Count</p>
            <input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
          </label>
          <label>
            <p>Gift Wrap</p>
            <input
             checked={formData['gift-wrap'] || false}
             disabled={formData.apple !== 'fuji'}
             name="gift-wrap"
             onChange={handleChange}
             type="checkbox"
            />
          </label>
        </fieldset>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Save the file. When you do, the browser will reload and the checkbox will be disabled by default:

Gift wrap is disabled

If you select the apple type of Fuji, the element will be enabled:

Gift wrap is enabled

In addition to changing properties on individual components, you can modify entire groups of components by updating the fieldset component.

As an example, you can disable the form while the form is actively submitting. This will prevent double submissions and prevent the user from changing fields before the handleSubmit function fully resolves.

Add disabled={submitting} to each <fieldset> element and the <button> element:

form-tutorial/src/components/App/App.js

...
      <form onSubmit={handleSubmit}>
        <fieldset disabled={submitting}>
          <label>
            <p>Name</p>
            <input name="name" onChange={handleChange} value={formData.name || ''}/>
          </label>
        </fieldset>
        <fieldset disabled={submitting}>
          <label>
            <p>Apples</p>
            <select name="apple" onChange={handleChange} value={formData.apple || ''}>
                <option value="">--Please choose an option--</option>
                <option value="fuji">Fuji</option>
                <option value="jonathan">Jonathan</option>
                <option value="honey-crisp">Honey Crisp</option>
            </select>
          </label>
          <label>
            <p>Count</p>
            <input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
          </label>
          <label>
            <p>Gift Wrap</p>
            <input
              checked={formData['gift-wrap'] || false}
              disabled={formData.apple !== 'fuji'}
              name="gift-wrap"
              onChange={handleChange}
              type="checkbox"
            />
          </label>
        </fieldset>
        <button type="submit" disabled={submitting}>Submit</button>
      </form>
    </div>
  )
}

export default App;

Save the file, and the browser will refresh. When you submit the form, the fields will be disabled until the submitting function resolves:

Disable form elements when submitting

You can update any attribute on an input component. This is helpful if you need to change the maxvalue for a number input or if you need to add a dynamic pattern attribute for validation.

In this step, you dynamically set attributes on form components. You added a property to dynamically enable or disable a component based on the input from another component and you disabled entire sections using the <fieldset> component.

Conclusion

Forms are key to rich web applications. In React, you have different options for connecting and controlling forms and elements. Like other components, you can dynamically update properties including the value input elements. Uncontrolled components are best for simplicity, but might not fit situations when a component needs to be cleared or pre-populated with data. Controlled components give you more opportunities to update the data, but can add another level of abstraction that may cause unintentional bugs or re-renders.

Regardless of your approach, React gives you the ability to dynamically update and adapt your forms to the needs of your application and your users.

If you would like to read more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.

https://mattboldt.com/2020/05/02/formdata-with-react-hooks-and-fetch/

###FormData with React Hooks and Fetch

May 02, 2020

REACTHOOKSUSEREFFORMDATA

 


Forms are hard. Forms in React are hard. For most cases, a 3rd party solution like react-hooks-form is the way to go. However, some may find themselves in a position where they cannot (or prefer not to) add a dependency, and simply want submit a no-frills html form, asynchronously, using only React.

Using setState

Consider the following plain React form:

const UserForm = props => {
  const [user, setUser] = useState(props.user)

  const submit = e => {
    e.preventDefault()
    fetch('/api', {
      method: 'POST',
      body: JSON.stringify({ user }),
      headers: { 'Content-Type': 'application/json' },
    })
      .then(res => res.json())
      .then(json => setUser(json.user))
  }

  return (
    <form onSubmit={submit}>
      <input
        type="text"
        name="user[name]"
        value={user.name}
        onChange={e => setUser({ ...user, name: e.target.value })}
      />
      {user.errors.name && <p>{user.errors.name}</p>}

      <input
        type="email"
        name="user[email]"
        value={user.email}
        onChange={e => setUser({ ...user, email: e.target.value })}
      />
      {user.errors.name && <p>{user.errors.name}</p>}

      <input type="submit" name="Sign Up" />
    </form>
  )
}
  • We're using useState to keep track of our state both before and after submission. Without this hook, we wouldn't know what to send to the API.
  • We must use controlled components with onChange handlers for each input, even though it's not necessary to keep track of changes for every keystroke.
  • Even though we're using a form element and inputs, we're not reaping any benefits from the built-in attributes like name.

Using FormData

Here's the same form with FormData

const UserForm = props => {
  const [user, setUser] = useState(props.user)
  const form = useRef(null)

  const submit = e => {
    e.preventDefault()
    const data = new FormData(form.current)
    fetch('/api', { method: 'POST', body: data })
      .then(res => res.json())
      .then(json => setUser(json.user))
  }

  return (
    <form ref={form} onSubmit={submit}>
      <input type="text" name="user[name]" defaultValue={user.name} />
      {user.errors.name && <p>{user.errors.name}</p>}

      <input type="email" name="user[email]" defaultValue={user.email} />
      {user.errors.email && <p>{user.errors.email}</p>}

      <input type="submit" name="Sign Up" />
    </form>
  )
}
  • Instead of sending data as JSON, we're sending it as a standard FormData object for the server to consume.
  • There was no need to use controlled inputs, so instead we're using defaultValue and letting that state remain unbound. This also means we don't need to update the state onChange.
  • We're letting each input's name attribute build up what gets sent to the API, instead of building an object and serializing JSON manually.
  • setUser is still called after the API responds, which can be helpful if we needed to attach errors from the API response.

A More Complex Example

What if our sign up form grows, and we need to add address fields? Using setState alone like in the first example could prove challenging with updating all that state and preparing it to be sent to the API. But with FormData, it becomes rather easy:

const UserForm = props => {
  const [user, setUser] = useState(props.user)
  const form = useRef(null)

  const submit = e => {
    e.preventDefault()

    const data = new FormData(form.current)
    fetch('/api', {
      method: 'POST',
      body: data,
    })
      .then(res => res.json())
      .then(json => setUser(json.user))
  }

  return (
    <form ref={form} onSubmit={submit}>
      <input type="text" name="user[name]" defaultValue={user.name} />
      {user.errors.name && <p>{user.errors.name}</p>}

      <input type="email" name="user[email]" defaultValue={user.email} />
      {user.errors.email && <p>{user.errors.email}</p>}

      {user.addresses.map((address, idx) => (
        <div key={idx}>
          <input
            type="text"
            name="user[addresses][][address]"
            defaultValue={address.address}
          />
          <input
            type="text"
            name="user[addresses][][city]"
            defaultValue={address.city}
          />
          <input
            type="text"
            name="user[addresses][][state]"
            defaultValue={address.state}
          />

          {user.errors.addresses[idx] && <p>{user.errors.addresses[idx]}</p>}
        </div>
      ))}

      <input type="submit" name="Sign Up" />
    </form>
  )
}
  • No changes to state were necessary to include our new address fields. They live in uncontrolled inputs, and required no complex state traversing or merging strategies to keep everything in sync.
  • Since our API (in my case a very small node Express server) knows how to parse FormData already, it can access the new fields just as easily as if it were JSON.
  • The state structure is tightly coupled to our form element, making it easier for another developer to come in and understand what's going on, add fields, or perform validation. They don't need to worry as much about the state or when/where it's updated.

Error when POSTing multipart/form-data

One issue I did run into was the setting of custom headers using fetch. Note the absence of a Content-Type header in the API request:

const submit = e => {
  e.preventDefault()

  const data = new FormData(form.current)
  fetch('/api', {
    method: 'POST',
    headers: new Headers({
      'AuthHeader': '123',
      // STOP! Do not add the following header!
      // 'Content-Type': 'multipart/form-data'
    }),
    body: data,
  })
    .then(res => res.json())
    .then(json => setUser(json.user))
}

After some time debugging, I found this Github issue for fetch. Essentially, when you post FormData via fetch, it automatically appends this header with some extra info. It looks something like this:

Content-Type: multipart/form-data;boundary=----WebKitFormBoundaryyrV7KO0BoCBuDbTL

Overriding this header results in a failed POST request, and a Bad Time™. So remember to exclude it when using FormData with fetch.

Using Reducer

https://www.codingdeft.com/posts/react-form-validation/

https://www.codingdeft.com/posts/react-form-validation/

 

Table of Contents

  1. Form Validation Library Comparison

    1. Redux Form
    2. Formik
    3. React Final Form
  2. Setting up the project

    1. Binding the form value with the state
    2. Adding form handler
    3. Adding Validations
    4. Displaying error message
    5. Adding validation to other fields
    6. Adding form level validation
  3. Analyzing the bundle size

  4. Conclusion

  5. Source code and Demo

Form Validation Library Comparison

There are a lot of libraries out there for validating forms in react. Redux-Form, Formik, react-final-form are few among them.

While these libraries are cool and they help in validating the forms to a great extent, they come with a catch: they add up to bundle size. Let's see a quick comparison between these libraries:

Redux Form

Redux form cannot function on its own. It has 2 additional dependencies redux and react-redux. If you are already using redux in your application, then you have already installed redux and react-redux packages. You can see that from the bundle phobia analysis given below that it adds 35 kB to your bundle size, while react itself is just about 38.5 kB.

redux form stats

Formik

Formik can function on its own without any additional packages to be installed along with it. The bundle size is 15 kB, which is considerably smaller than that of redux-form.

formik stats

React Final Form

React final form is created by the author (@erikras) of redux-form. It is a wrapper around the final-form core, which has no dependencies. Since one of the goals behind react final forms was to reduce bundle size, it weighs 8.5 kB gzipped.

react final form

Now let's see how we can do form validation without depending upon these libraries:

Setting up the project

Create a new react project using the following command:

 

1npx create-react-app react-form-validation

Update App.js with the following code:

 

App.js

1import React from "react"
2import "./App.css"
3

4function App() {
5  return (
6    <div className="App">
7      <h1 className="title">Sign Up</h1>
8      <form>
9        <div className="input_wrapper">
10          <label htmlFor="name">Name:</label>
11          <input type="text" name="name" id="name" />
12        </div>
13        <div className="input_wrapper">
14          <label htmlFor="email">Email:</label>
15          <input type="email" name="email" id="email" />
16        </div>
17        <div className="input_wrapper">
18          <label htmlFor="password">Password:</label>
19          <input type="password" name="password" id="password" />
20        </div>
21        <div className="input_wrapper">
22          <label htmlFor="mobile">Mobile:</label>
23          <input type="text" name="mobile" id="mobile" />
24        </div>
25        <div className="input_wrapper">
26          <label className="toc">
27            <input type="checkbox" name="terms" /> Accept terms and conditions
28          </label>
29        </div>
30        <div className="input_wrapper">
31          <input className="submit_btn" type="submit" value="Sign Up" />
32        </div>
33      </form>
34    </div>
35  )
36}
37

38export default App

Here we have created a simple sign up form with few fields. Now to style these fields let's add some css:

 

App.css

1.App {
2  max-width: 300px;
3  margin: 1rem auto;
4}
5.title {
6  text-align: center;
7}
8.input_wrapper {
9  display: flex;
10  flex-direction: column;
11  margin-bottom: 0.5rem;
12}
13.input_wrapper label {
14  font-size: 1.1rem;
15}
16.input_wrapper input {
17  margin-top: 0.4rem;
18  font-size: 1.1rem;
19}
20.submit_btn {
21  cursor: pointer;
22  padding: 0.2rem;
23}
24.toc,
25.toc input {
26  cursor: pointer;
27}

Now if you open the app, you should see our basic form set up:

basic form

Binding the form value with the state

Now that we have the form ready, let's bind the input values with the state

 

App.js

1import React, { useReducer } from "react"
2import "./App.css"
3

4/**
5 * The initial state of the form
6 * value: stores the value of the input field
7 * touched: indicates whether the user has tried to input anything in the field
8 * hasError: determines whether the field has error.
9 *           Defaulted to true since all fields are mandatory and are empty on page load.
10 * error: stores the error message
11 * isFormValid: Stores the validity of the form at any given time.
12 */
13const initialState = {
14  name: { value: "", touched: false, hasError: true, error: "" },
15  email: { value: "", touched: false, hasError: true, error: "" },
16  password: { value: "", touched: false, hasError: true, error: "" },
17  mobile: { value: "", touched: false, hasError: true, error: "" },
18  terms: { value: false, touched: false, hasError: true, error: "" },
19  isFormValid: false,
20}
21

22/**
23 * Reducer which will perform form state update
24 */
25const formsReducer = (state, action) => {
26  return state
27}
28

29function App() {
30  const [formState, dispatch] = useReducer(formsReducer, initialState)
31

32  return (
33    <div className="App">
34      <h1 className="title">Sign Up</h1>
35      <form>
36        <div className="input_wrapper">
37          <label htmlFor="name">Name:</label>
38          <input
39            type="text"
40            name="name"
41            id="name"
42            value={formState.name.value}
43          />
44        </div>
45        <div className="input_wrapper">
46          <label htmlFor="email">Email:</label>
47          <input
48            type="email"
49            name="email"
50            id="email"
51            value={formState.email.value}
52          />
53        </div>
54        <div className="input_wrapper">
55          <label htmlFor="password">Password:</label>
56          <input
57            type="password"
58            name="password"
59            id="password"
60            value={formState.password.value}
61          />
62        </div>
63        <div className="input_wrapper">
64          <label htmlFor="mobile">Mobile:</label>
65          <input
66            type="text"
67            name="mobile"
68            id="mobile"
69            value={formState.mobile.value}
70          />
71        </div>
72        <div className="input_wrapper">
73          <label className="toc">
74            <input
75              type="checkbox"
76              name="terms"
77              checked={formState.terms.value}
78            />{" "}
79            Accept terms and conditions
80          </label>
81        </div>
82        <div className="input_wrapper">
83          <input className="submit_btn" type="submit" value="Sign Up" />
84        </div>
85      </form>
86    </div>
87  )
88}
89

90export default App

In the above code,

  • We have introduced a new object initialState, which stores the initial state of the form.
  • We also have defined a reducer function named formsReducer, which does nothing as of now, but we will have the logic inside it to update the form state.
  • We have introduced useReducer hook, which returns the current form state and a dispatch function, which will be used to fire form update actions.

If you try to enter any values in the form now, you will not be able to update it because we don't have any handler functions, which will update our state.

Adding form handler

Create a folder called lib in src directory and a file named formUtils.js inside it. This file will have the handler functions which can be reused for other forms.

 

formUtils.js

1export const UPDATE_FORM = "UPDATE_FORM"
2

3/**
4 * Triggered every time the value of the form changes
5 */
6export const onInputChange = (name, value, dispatch, formState) => {
7  dispatch({
8    type: UPDATE_FORM,
9    data: {
10      name,
11      value,
12      hasError: false,
13      error: "",
14      touched: false,
15      isFormValid: true,
16    },
17  })
18}

Here you could see that we are dispatching the UPDATE_FORM action with the value that is being passed to the handler. As of now, we are setting hasError to false and isFormValid to true since we are yet to write the validation logic.

Now in the App.js file, update the reducer function to handle the UPDATE_FORM action. Here we are updating the value of the corresponding input field using the name as the key.

 

formReducer function

1//...
2

3import { UPDATE_FORM, onInputChange } from "./lib/formUtils"
4

5//...
6const formsReducer = (state, action) => {
7  switch (action.type) {
8    case UPDATE_FORM:
9      const { name, value, hasError, error, touched, isFormValid } = action.data
10      return {
11        ...state,
12        // update the state of the particular field,
13        // by retaining the state of other fields
14        [name]: { ...state[name], value, hasError, error, touched },
15        isFormValid,
16      }
17    default:
18      return state
19  }
20}

Now bind the onInputChange handler we imported above with the input field for name:

 

1<div className="input_wrapper">
2  <label htmlFor="name">Name:</label>
3  <input
4    type="text"
5    name="name"
6    id="name"
7    value={formState.name.value}
8    onChange={e => {
9      onInputChange("name", e.target.value, dispatch, formState)
10    }}
11  />
12</div>

Now you should be able to edit the name field. Now its time to write the validation logic!

Adding Validations

Add a function called validateInput to formUtils.js. Inside this function, we will write validations for all the fields.

 

1export const validateInput = (name, value) => {
2  let hasError = false,
3    error = ""
4  switch (name) {
5    case "name":
6      if (value.trim() === "") {
7        hasError = true
8        error = "Name cannot be empty"
9      } else if (!/^[a-zA-Z ]+$/.test(value)) {
10        hasError = true
11        error = "Invalid Name. Avoid Special characters"
12      } else {
13        hasError = false
14        error = ""
15      }
16      break
17    default:
18      break
19  }
20  return { hasError, error }
21}

Here you can see that in the first if condition, we are checking for empty value since name field is mandatory. In the second if condition, we are using RegEx to validate if the name contains any other characters other than the English alphabets and spaces.

We assume that all the names are in English. If you have a user base in regions where the name contains characters outside the English alphabet, you can update the Regex accordingly.

Now update the onInputChange function to make use of the validation function:

 

1export const onInputChange = (name, value, dispatch, formState) => {
2  const { hasError, error } = validateInput(name, value)
3  let isFormValid = true
4

5  for (const key in formState) {
6    const item = formState[key]
7    // Check if the current field has error
8    if (key === name && hasError) {
9      isFormValid = false
10      break
11    } else if (key !== name && item.hasError) {
12      // Check if any other field has error
13      isFormValid = false
14      break
15    }
16  }
17

18  dispatch({
19    type: UPDATE_FORM,
20    data: { name, value, hasError, error, touched: false, isFormValid },
21  })
22}

You will also see that we are looping through the formState to check if any of the field is having error to determine the overall validity of the form.

Now let's see if our validation logic works fine. Since we aren't displaying the error message yet, let's log the formState and see the values.

When an invalid name is entered

Invalid Name Validation

Use console.table({"name state": formState.name}); for displaying the values of an object in tabular format

When the name is kept empty

Empty Name Validation

When a valid name is entered

valid Name

You might see that the loggers are getting printed twice each time you type a character: this is expected to happen in Strict Mode in the development environment. This is actually to make sure that there are no side effects inside the reducer functions.

Displaying error message

Before showing the error message, let's add another handler function to our formUtils.js

 

1//...
2export const onFocusOut = (name, value, dispatch, formState) => {
3  const { hasError, error } = validateInput(name, value)
4  let isFormValid = true
5  for (const key in formState) {
6    const item = formState[key]
7    if (key === name && hasError) {
8      isFormValid = false
9      break
10    } else if (key !== name && item.hasError) {
11      isFormValid = false
12      break
13    }
14  }
15

16  dispatch({
17    type: UPDATE_FORM,
18    data: { name, value, hasError, error, touched: true, isFormValid },
19  })
20}

You might observe that the onFocusOut function is very similar to onInputChange, except that we pass touched as true in case of onFocusOut. The reason for having additional handler function, which will be bound with the onBlur event of the input is to show the error messages only when the user finishes typing and moves to the next field.

Now that we have the error message stored in our state, let's display it:

 

App.js

1//...
2import { UPDATE_FORM, onInputChange, onFocusOut } from "./lib/formUtils"
3

4//...
5function App() {
6  const [formState, dispatch] = useReducer(formsReducer, initialState)
7

8  return (
9    <div className="App">
10      <h1 className="title">Sign Up</h1>
11      <form>
12        <div className="input_wrapper">
13          <label htmlFor="name">Name:</label>
14          <input
15            type="text"
16            name="name"
17            id="name"
18            value={formState.name.value}
19            onChange={e => {
20              onInputChange("name", e.target.value, dispatch, formState)
21            }}
22            onBlur={e => {
23              onFocusOut("name", e.target.value, dispatch, formState)
24            }}
25          />
26          {formState.name.touched && formState.name.hasError && (
27            <div className="error">{formState.name.error}</div>
28          )}
29        </div>
30        {/* ... */}
31      </form>
32    </div>
33  )
34}
35

36export default App

You will see that we have added onBlur handler and we are displaying the error message whenever the form is touched and has errors.

Now let's add some styling for the error message

 

App.css

1/*...*/
2.error {
3  margin-top: 0.25rem;
4  color: #f65157;
5}

Now if you type an invalid name or leave the field empty, you will see the error message:

Name Validations

Adding validation to other fields

Now let's add validation to other fields

Update the validateInput function inside formUtils.js:

 

formUtils.js

1export const validateInput = (name, value) => {
2  let hasError = false,
3    error = ""
4  switch (name) {
5    case "name":
6      if (value.trim() === "") {
7        hasError = true
8        error = "Name cannot be empty"
9      } else if (!/^[a-zA-Z ]+$/.test(value)) {
10        hasError = true
11        error = "Invalid Name. Avoid Special characters"
12      } else {
13        hasError = false
14        error = ""
15      }
16      break
17    case "email":
18      if (value.trim() === "") {
19        hasError = true
20        error = "Email cannot be empty"
21      } else if (
22        !/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(
23          value
24        )
25      ) {
26        hasError = true
27        error = "Invalid Email"
28      } else {
29        hasError = false
30        error = ""
31      }
32      break
33    case "password":
34      if (value.trim() === "") {
35        hasError = true
36        error = "Password cannot be empty"
37      } else if (value.trim().length < 8) {
38        hasError = true
39        error = "Password must have at least 8 characters"
40      } else {
41        hasError = false
42        error = ""
43      }
44      break
45    case "mobile":
46      if (value.trim() === "") {
47        hasError = true
48        error = "Mobile cannot be empty"
49      } else if (!/^[0-9]{10}$/.test(value)) {
50        hasError = true
51        error = "Invalid Mobile Number. Use 10 digits only"
52      } else {
53        hasError = false
54        error = ""
55      }
56      break
57    case "terms":
58      if (!value) {
59        hasError = true
60        error = "You must accept terms and conditions"
61      } else {
62        hasError = false
63        error = ""
64      }
65      break
66    default:
67      break
68  }
69  return { hasError, error }
70}

Note that we have added validation password to have minimum of 8 characters, mobile number to have 10 digits. Also, you might be wondering about the really long RegEx used for email validation. You can read more about email validation at emailregex.com.

Now let's bind them to the form:

 

App.js

1//...
2

3function App() {
4  const [formState, dispatch] = useReducer(formsReducer, initialState)
5

6  return (
7    <div className="App">
8      <h1 className="title">Sign Up</h1>
9      <form>
10        <div className="input_wrapper">
11          <label htmlFor="name">Name:</label>
12          <input
13            type="text"
14            name="name"
15            id="name"
16            value={formState.name.value}
17            onChange={e => {
18              onInputChange("name", e.target.value, dispatch, formState)
19            }}
20            onBlur={e => {
21              onFocusOut("name", e.target.value, dispatch, formState)
22            }}
23          />
24          {formState.name.touched && formState.name.hasError && (
25            <div className="error">{formState.name.error}</div>
26          )}
27        </div>
28        <div className="input_wrapper">
29          <label htmlFor="email">Email:</label>
30          <input
31            type="email"
32            name="email"
33            id="email"
34            value={formState.email.value}
35            onChange={e => {
36              onInputChange("email", e.target.value, dispatch, formState)
37            }}
38            onBlur={e => {
39              onFocusOut("email", e.target.value, dispatch, formState)
40            }}
41          />
42          {formState.email.touched && formState.email.hasError && (
43            <div className="error">{formState.email.error}</div>
44          )}
45        </div>
46        <div className="input_wrapper">
47          <label htmlFor="password">Password:</label>
48          <input
49            type="password"
50            name="password"
51            id="password"
52            value={formState.password.value}
53            onChange={e => {
54              onInputChange("password", e.target.value, dispatch, formState)
55            }}
56            onBlur={e => {
57              onFocusOut("password", e.target.value, dispatch, formState)
58            }}
59          />
60          {formState.password.touched && formState.password.hasError && (
61            <div className="error">{formState.password.error}</div>
62          )}
63        </div>
64        <div className="input_wrapper">
65          <label htmlFor="mobile">Mobile:</label>
66          <input
67            type="text"
68            name="mobile"
69            id="mobile"
70            value={formState.mobile.value}
71            onChange={e => {
72              onInputChange("mobile", e.target.value, dispatch, formState)
73            }}
74            onBlur={e => {
75              onFocusOut("mobile", e.target.value, dispatch, formState)
76            }}
77          />
78          {formState.mobile.touched && formState.mobile.hasError && (
79            <div className="error">{formState.mobile.error}</div>
80          )}
81        </div>
82        <div className="input_wrapper">
83          <label className="toc">
84            <input
85              type="checkbox"
86              name="terms"
87              checked={formState.terms.value}
88              onChange={e => {
89                onFocusOut("terms", e.target.checked, dispatch, formState)
90              }}
91            />
92            Accept terms and conditions
93          </label>
94          {formState.terms.touched && formState.terms.hasError && (
95            <div className="error">{formState.terms.error}</div>
96          )}
97        </div>
98        <div className="input_wrapper">
99          <input className="submit_btn" type="submit" value="Sign Up" />
100        </div>
101      </form>
102    </div>
103  )
104}
105

106export default App

Now if you test the application, you will see all validations in place:

All Validations

Though we have all the validations, we are not validating the form if the user clicks on submit without filling any of the fields.

Adding form level validation

For the last time, let's add the form level validation

 

App.js

1import React, { useReducer, useState } from "react"
2import "./App.css"
3import {
4  UPDATE_FORM,
5  onInputChange,
6  onFocusOut,
7  validateInput,
8} from "./lib/formUtils"
9

10//...
11

12function App() {
13  const [formState, dispatch] = useReducer(formsReducer, initialState)
14

15  const [showError, setShowError] = useState(false)
16

17  const formSubmitHandler = e => {
18    e.preventDefault() //prevents the form from submitting
19

20    let isFormValid = true
21

22    for (const name in formState) {
23      const item = formState[name]
24      const { value } = item
25      const { hasError, error } = validateInput(name, value)
26      if (hasError) {
27        isFormValid = false
28      }
29      if (name) {
30        dispatch({
31          type: UPDATE_FORM,
32          data: {
33            name,
34            value,
35            hasError,
36            error,
37            touched: true,
38            isFormValid,
39          },
40        })
41      }
42    }
43    if (!isFormValid) {
44      setShowError(true)
45    } else {
46      //Logic to submit the form to backend
47    }
48

49    // Hide the error message after 5 seconds
50    setTimeout(() => {
51      setShowError(false)
52    }, 5000)
53  }
54

55  return (
56    <div className="App">
57      <h1 className="title">Sign Up</h1>
58      {showError && !formState.isFormValid && (
59        <div className="form_error">Please fill all the fields correctly</div>
60      )}
61      <form onSubmit={e => formSubmitHandler(e)}>
62        <div className="input_wrapper">{/* ... */}</div>
63      </form>
64    </div>
65  )
66}
67

68export default App

We have added a block error message which will be displayed when the user submits the form and as long as the form is invalid.

Let's add some css to style the error message:

 

App.css

1/* ... */
2

3.form_error {
4  color: #721c24;
5  background-color: #f8d7da;
6  border-color: #f5c6cb;
7  padding: 0.5rem 1.25rem;
8  border: 1px solid transparent;
9  border-radius: 0.25rem;
10  margin: 1rem 0;
11}

Now if you click on the submit button without filling the form you should see:

Block Validation Message

Analyzing the bundle size

Let's see if we were successful in reducing the bundle size by writing our own implementation of form validation. To do so, first install webpack-bundle-analyzer package as a dev dependency:

 

1yarn add webpack-bundle-analyzer -D

Create a file named analyze.js in the root directory with the following content:

 

analyze.js

1// script to enable webpack-bundle-analyzer
2process.env.NODE_ENV = "production"
3const webpack = require("webpack")
4const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
5  .BundleAnalyzerPlugin
6const webpackConfigProd = require("react-scripts/config/webpack.config")(
7  "production"
8)
9

10webpackConfigProd.plugins.push(new BundleAnalyzerPlugin())
11

12// actually running compilation and waiting for plugin to start explorer
13webpack(webpackConfigProd, (err, stats) => {
14  if (err || stats.hasErrors()) {
15    console.error(err)
16  }
17})

Run the following command in the terminal:

 

1node analyze.js

Now a browser window will automatically open with the URL http://127.0.0.1:8888

If you see the bundle size, you will find that our application including form validation utils and css is just 1.67kB gzipped!

Bundle Size

Conclusion

While the form validation libraries have a lot of advantages like it lets you write less code and if there are a lot of forms in your application, it pays for itself. But if you are having a simple form and you are concerned about bundle size you can always go for this custom implementation. Also, if the form is very complex, then again you will have to go for custom implementation since the form validation libraries might not cover all your use cases.

Source code and Demo

You can view the complete source code here and a demo here.



Simple Form Validation

https://stackoverflow.com/questions/41296668/reactjs-form-input-validation

On every change, update the state for the changed field.
Then you can easily check if that field is empty or whatever else you want.
You could do something as follows :


class Test extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      fields: {},
      errors: {},
    };
  }

  handleValidation() {
    let fields = this.state.fields;
    let errors = {};
    let formIsValid = true;

    //Name
    if (!fields["name"]) {
      formIsValid = false;
      errors["name"] = "Cannot be empty";
    }

    if (typeof fields["name"] !== "undefined") {
      if (!fields["name"].match(/^[a-zA-Z]+$/)) {
        formIsValid = false;
        errors["name"] = "Only letters";
      }
    }

    //Email
    if (!fields["email"]) {
      formIsValid = false;
      errors["email"] = "Cannot be empty";
    }

    if (typeof fields["email"] !== "undefined") {
      let lastAtPos = fields["email"].lastIndexOf("@");
      let lastDotPos = fields["email"].lastIndexOf(".");

      if (
        !(
          lastAtPos < lastDotPos &&
          lastAtPos > 0 &&
          fields["email"].indexOf("@@") == -1 &&
          lastDotPos > 2 &&
          fields["email"].length - lastDotPos > 2
        )
      ) {
        formIsValid = false;
        errors["email"] = "Email is not valid";
      }
    }

    this.setState({ errors: errors });
    return formIsValid;
  }

  contactSubmit(e) {
    e.preventDefault();

    if (this.handleValidation()) {
      alert("Form submitted");
    } else {
      alert("Form has errors.");
    }
  }

  handleChange(field, e) {
    let fields = this.state.fields;
    fields[field] = e.target.value;
    this.setState({ fields });
  }

  render() {
    return (
      
{this.state.errors["name"]}
{this.state.errors["email"]}


); } } React.render(, document.getElementById("container")); In this example I did the validation only for email and name, but you have an idea how to do it. For the rest you can do it self. There is maybe a better way, but you will get the idea. Here is fiddle.

https://www.techiediaries.com/php-react-rest-api-crud-tutorial/

HP, MySQL & React REST API Tutorial with Example Form

Throughout this tutorial, we'll be using PHP with React and Axios to create a simple REST API application with CRUD operations. In the backend we'll use PHP with a MySQL database.

The PHP backend will expose a set of RESTful API endpoints so we'll be using the Axios library for making Ajax calls from the React.js UI.

We'll also see how to handle forms in React and how to send multipart form data with Axios using FormData.

In this tutorial, we are going to integrate React with PHP using Babel in the browser and a <script> tag. As such, we'll serve the React application from PHP so we don't need to enable CORS in our server since both the backend and frontend are served from the same domain.

We'll see the other approach of using two separate servers for the frontend and backend apps in another tutorial which will use the create-react-app to create the React project.

Prerequisites

You must have the following prerequsites in order to follow this tutorial comfortably:

  • Knowledge of PHP and MySQL,
  • Knowledge of JavaScript and React,
  • PHP and MySQL installed on your development machine.

Creating the MySQL Database

Let's start by creating a MySQL database using the MySQL client (this usually gets installed when you install the MySQL server). Open a new terminal and run the following command:

mysql -u root -p

You'll be asked for your MySQL password. Make sure to submit the correct password and type Enter on your keyboard to confirm.

Next, you'll be presetend with the MySQL client CLI. You can create a database using the following SQL statement:

mysql> create database reactdb;

Next, let's add a SQL table in our database. Simpy run the following SQL instructions:

mysql> use reactdb;
mysql> CREATE TABLE `contacts` (
  `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `email` varchar(100) NOT NULL,
  `city` varchar(100),
  `country` varchar(100),
  `job` varchar(100)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

We first run the use SQL instruction to select the reactdb database as our current working database. Next, we invoke the CREATE TABLE <name_of_table> statement to create a SQL table that has the following columns:

  • id: A unique identifier for the person,
  • name: The name of the person,
  • email: The email for the person,
  • city: The city of the person
  • country: The country of the person
  • job: The job occupied by the person

Basically, this is a simple database for managing your contacts data.

Creating The PHP & MySQL RESTful API

After creating the MySQL database, table and columns. Let's now proceed to create a RESTful API interface exposed by a PHP application that runs CRUD operations against our previously-created MySQL table. Head back to your terminal and start by creating a directory for your project's files:

$ cd ~
$ mkdir php-react-rest-api-crud

Create a REST API Endpoint

Now, let's create an endpoint that provides contacts data in a JSON format to our Vue frontend.

Create an api folder inside your project's root folder:

$ mkdir api

Navigate inside the api folder and create a contacts.php file and add the following content:

<?php
$host = "localhost"; 
$user = "root"; 
$password = "YOUR_MYSQL_DB_PASSWORD"; 
$dbname = "reactdb"; 
$id = '';

$con = mysqli_connect($host, $user, $password,$dbname);

$method = $_SERVER['REQUEST_METHOD'];
$request = explode('/', trim($_SERVER['PATH_INFO'],'/'));


if (!$con) {
  die("Connection failed: " . mysqli_connect_error());
}


switch ($method) {
    case 'GET':
      $id = $_GET['id'];
      $sql = "select * from contacts".($id?" where id=$id":''); 
      break;
    case 'POST':
      $name = $_POST["name"];
      $email = $_POST["email"];
      $country = $_POST["country"];
      $city = $_POST["city"];
      $job = $_POST["job"];

      $sql = "insert into contacts (name, email, city, country, job) values ('$name', '$email', '$city', '$country', '$job')"; 
      break;
}

// run SQL statement
$result = mysqli_query($con,$sql);

// die if SQL statement failed
if (!$result) {
  http_response_code(404);
  die(mysqli_error($con));
}

if ($method == 'GET') {
    if (!$id) echo '[';
    for ($i=0 ; $i<mysqli_num_rows($result) ; $i++) {
      echo ($i>0?',':'').json_encode(mysqli_fetch_object($result));
    }
    if (!$id) echo ']';
  } elseif ($method == 'POST') {
    echo json_encode($result);
  } else {
    echo mysqli_affected_rows($con);
  }

$con->close();

We first use the MySQLi PHP extension to create a connection to our MySQL database using the mysqli_connect() method. Next, we use the $_SERVER['REQUEST_METHOD'] to retrieve the request method sent from the Axios client. If the request is GET, we create a SQL SELECT query. if the request is POST we create a SQL INSERT query with the post data retrieved from the $_POST object.

After that, we use the mysqli_query() method to run the query against our database table either to get or create data. Finally we use the json_encode() method to encode data as JSON data and send it to the client.

You can serve your PHP application using the following command from the root of your project:

$ php -S 127.0.0.1:8080

Create the React App

Next, navigate to the project's root folder and add an index.php file:

$ touch index.php

Next, open the index.php file and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>PHP| MySQL | React.js | Axios Example</title>
    <script src= "https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script src= "https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <!-- Load Babel Compiler -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

</head>
<body>
</body>
</html>

We simply include the React, ReactDOM, Babel and Axios libraries from their CDNs.

Next, in the index.html, in the <body> tag add a <div> tag where you can mount your React application:

<div id='root'></div>

Next, add a <script> tag of the text/babel type to create our React app:

<body>
<div id='root'></div>

<script  type="text/babel">

class App extends React.Component {
  state = {
    contacts: []
  }
  render() {
    return (
        <React.Fragment>
        <h1>Contact Management</h1>
        <table border='1' width='100%' >
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Country</th>
            <th>City</th>
            <th>Job</th>     
        </tr>

        {this.state.contacts.map((contact) => (
        <tr>
            <td>{ contact.name }</td>
            <td>{ contact.email }</td>
            <td>{ contact.country }</td>
            <td>{ contact.city }</td>
            <td>{ contact.job }</td>
        </tr>
        ))}
        </table>
        </React.Fragment>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>   

We first create a React component called App by extending the React.Component class. Next, we add a contacts variable to the state object which will be used to hold the contacts after we fetch them from the PHP REST endpoint using Axios.

Next, we define a React render() method which returns a fragment that wraps the <h1> header and <table> elements.

In the table we loop through the this.state.contacts and we display each <tr> corresponding to each contact information.

Finally, we use the render() method of ReactDOM to actually mount our App component to the DOM.

The contacts array is empty. Let's use the Axios client to send a GET request to fetch data from /api/contacts.php endpoint exposed by the PHP server.

In the App component add a componentDidMount() life cycle method, which gets called when the component is mounted in the DOM, and inside it; add the code to fetch data:

  componentDidMount() {
    const url = '/api/contacts.php'
    axios.get(url).then(response => response.data)
    .then((data) => {
      this.setState({ contacts: data })
      console.log(this.state.contacts)
     })
  }

When data is fetched, we call the React setState method to update the state of the component with the fetched data.

Create a React Form for Submitting Data

Let's now add a React component that displays a form and handles submitting the form to the PHP backend. In your index.php file add the following component before the App component:

class ContactForm extends React.Component {
    state = {
        name: '',
        email: '',
        country: '',
        city: '',
        job: '',

    }

    handleFormSubmit( event ) {
        event.preventDefault();
        console.log(this.state);
    }

    render(){
        return (
        <form>
            <label>Name</label>
            <input type="text" name="name" value={this.state.name}
                onChange={e => this.setState({ name: e.target.value })}/>

            <label>Email</label>
            <input type="email" name="email" value={this.state.email}
                onChange={e => this.setState({ email: e.target.value })}/>

            <label>Country</label>
            <input type="text" name="country" value={this.state.country}
                onChange={e => this.setState({ country: e.target.value })}/>

            <label>City</label>
            <input type="text" name="city" value={this.state.city}
                onChange={e => this.setState({ city: e.target.value })}/>

            <label>Job</label>
            <input type="text" name="job" value={this.state.job}
                onChange={e => this.setState({ job: e.target.value })}/>

            <input type="submit" onClick={e => this.handleFormSubmit(e)} value="Create Contact" />
        </form>);
    }
}

Next include it in the App component to be able to display it below the table:

class App extends React.Component {
  // [...]
  render() {
    return (
        <React.Fragment>
            <!-- [...] -->
            <ContactForm />
        </React.Fragment>
    );
  }
}

Now let's change the handleFormSubmit() of ContactForm method to actually send the form data using Axios and FormData to our PHP REST endpoint which takes care of saving it in the MySQL database:

    handleFormSubmit( event ) {
        event.preventDefault();


        let formData = new FormData();
        formData.append('name', this.state.name)
        formData.append('email', this.state.email)
        formData.append('city', this.state.city)
        formData.append('country', this.state.country)
        formData.append('job', this.state.job)

        axios({
            method: 'post',
            url: '/api/contacts.php',
            data: formData,
            config: { headers: {'Content-Type': 'multipart/form-data' }}
        })
        .then(function (response) {
            //handle success
            console.log(response)

        })
        .catch(function (response) {
            //handle error
            console.log(response)
        });
    }

Conclusion

In this tutorial, we've seen how to use PHP with MySQL, React and Axios to create a simple REST API CRUD example application. We have also seen how to handle forms in React and submit data to the server.

https://www.techiediaries.com/formdata/

In this post, we'll learn about the FormData interface available in modern web browsers as a part of the HTML5 spec.

We'll see examples of using FormData with Ajax, Angular 9, Ionic 5 and React.

What's FormData

FormData is simply a data structure that can be used to store key-value pairs. Just like its name suggests it's designed for holding forms data i.e you can use it with JavaScript to build an object that corresponds to an HTML form. It's mostly useful when you need to send form data to RESTful API endpoints, for example to upload single or multiple files using the XMLHttpRequest interface, the fetch() API or Axios.

You can create a FormData object by instantiating the FormData interface using the new operator as follows:

const formData = new FormData()

The formData reference refers to an instance of FormData. You can call many methods on the object to add and work with pairs of data. Each pair has a key and value.

These are the available methods on FormData objects:

  • append() : used to append a key-value pair to the object. If the key already exists, the value is appended to the original value for that key,
  • delete(): used to deletes a key-value pair,
  • entries(): returns an Iterator object that you can use to loop through the list the key value pairs in the object,
  • get(): used to return the value for a key. If multiple values are appended, it returns the first value,
  • getAll(): used to return all the values for a specified key,
  • has(): used to check if there’s a key,
  • keys(): returns an Iterator object which you can use to list the available keys in the object,
  • set(): used to add a value to the object, with the specified key. This is going to relace the value if a key already exists,
  • values(): returns an Iterator object for the values of the FormData object.

File Upload Example with Vanilla JavaScript

Let's now see a simple example of file upload using vanilla JavaScript, XMLHttpRequest and FormData.

Navigate to your working folder and create and index.html file with the following content:

<!DOCTYPE html>
<html>

<head>
    <title>Parcel Sandbox</title>
    <meta charset="UTF-8" />
</head>

<body>
    <div id="app"></div>

    <script src="index.js">
    </script>
</body>

</html>

We simply create an HTML document with a <div> identified by the app ID. Next, we include the index.js file using a <script> tag.

Next, create the index.js file and add following code:

document.getElementById("app").innerHTML = `
<h3>File Upload & FormData Example</h3>
<div>
<input type="file" id="fileInput" />
</div>
`;

const fileInput = document.querySelector("#fileInput");

const uploadFile = file => {
  console.log("Uploading file...");
  const API_ENDPOINT = "https://file.io";
  const request = new XMLHttpRequest();
  const formData = new FormData();

  request.open("POST", API_ENDPOINT, true);
  request.onreadystatechange = () => {
    if (request.readyState === 4 && request.status === 200) {
      console.log(request.responseText);
    }
  };
  formData.append("file", file);
  request.send(formData);
};

fileInput.addEventListener("change", event => {
  const files = event.target.files;
  uploadFile(files[0]);
});

We first insert an <input type="file" id="fileInput" /> element in our HTML page. This will be used to select the file that we'll be uploading.

Next, we query for the file input element using the querySelector() method.

Next, we define the uploadFile() method in which we first declare an API_ENDPOINT variable that holds the address of our file uploading endpoint. Next, we create an XMLHttpRequest request and an empty FormData object.

We use the append method of FormData to append the file, passed as a parameter to the uploadFile() method, to the file key. This will create a key-value pair with file as a key and the content of the passed file as a value.

Next, we send the request using the send() method of XMLHttpRequest and we pass in the FormData object as an argument.

After defining the uploadFile() method, we listen for the change event on the <input> element and we call the uploadFile() method with the selected file as an argument. The file is accessed from event.target.files array.

You can experiment with this example from this code sandbox:

Uploading Multiple Files

You can easily modify the code above to support multiple file uploading.

First, you need to add the multiple property to the <input> element:

<input type="file" id="fileInput" multiple />

Now, you'll be able to select multiple files from your drive.

Next, change the uploadFile() method to accept an array of files as an argument and simply loop through the array and append the files to the FormData object:

const uploadFile = (files) => {
  console.log("Uploading file...");
  const API_ENDPOINT = "https://file.io";
  const request = new XMLHttpRequest();
  const formData = new FormData();

  request.open("POST", API_ENDPOINT, true);
  request.onreadystatechange = () => {
    if (request.readyState === 4 && request.status === 200) {
      console.log(request.responseText);
    }
  };

  for (let i = 0; i < files.length; i++) {
    formData.append(files[i].name, files[i])
  }
  request.send(formData);
};

Finally, call the method with an array of files as argument:

fileInput.addEventListener("change", event => {
  const files = event.target.files;
  uploadFile(files);
});

Next, you can check out these advanced tutorials for how to use FormData with Angular, Ionic and React:

Scroll to Top