DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report

Working With Forms in React.js Using The Basic Toolkit

A tutorial on using React.js with its built-in toolkit and how to use the concept of High Order Components in your JavaScript code.

Alex Maison user avatar by
Alex Maison
·
Sep. 05, 18 · Tutorial
Like (4)
Save
Tweet
Share
13.28K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

During my work with React.js, I often had to deal with the processing of forms. I tried using Redux-Form, React-Redux-Form, but none of the libraries suited all my needs. I did not like that the state of the form is stored in the reducer, and each event passes through the action creator. Also, according to Dan Abramov, "the state of the form is inherently ephemeral and local, so you do not need to track it in Redux (or any Flux library)."

I note that React-Redux-Form has a LocalForm component that allows developers to work without redux, but, in my opinion, it's useless to install a 21.9KB library and use it for less than half the time.

I'm not against these libraries, in specific cases they are irreplaceable. For example, when the third-party component that is not associated with the form is dependent on the data entered. But, in my article, I want to consider forms that do not need Redux.

I started using the local state component, and new difficulties arose: the amount of code increased, the components lost readability, and a lot of duplication appeared.

The solution to the problems was the concept of High Order Components. Briefly, HOC is a function that receives a component on the input and returns it, updated, with the integration of additional or changed props. More information about HOC can be found on the official website of React.js. The purpose of using the concept of HOC was to split the component into two parts, one of which would be responsible for the logic, and the second - for the mapping.

Creating a Form

As an example, we will create a simple feedback form, in which there will be 3 fields: name, email, phone.

For simplicity, use the Create-React-App. Establish it globally:

npm i -g create-react-app

Then create your application in a clean form folder:

create-react-app pure-form

In addition, we set prop-types and class names, as they will be useful in the future:

npm i prop-types classnames -S

Create two folders: /components and /containers. The /components folder will contain all the components that are responsible for the mapping. In the /containers folder, the components responsible for the logic.

In the /components folder, create the file Input.jsx, in which we declare the common component for all the intuitions. It is important at this stage not to forget to qualitatively register ProptTypes and defaultProps, to provide the ability to add custom classes, and also to inherit it from PureComponent for optimization.

The result is:

import React, { PureComponent } from 'react';
import cx from 'classnames';
import PropTypes from 'prop-types';

class Input extends PureComponent {
  render() {
    const {
      name,
      error,
      labelClass,
      inputClass,
      placeholder,
      ...props
    } = this.props;
    return (
      <label
        className={cx('label', !!labelClass && labelClass)}
        htmlFor={`id-${name}`}
      >
        <span className="span">{placeholder}</span>
        <input
          className={cx(
            'input',
            !!inputClass && inputClass,
            !!error && 'error'
          )}
          name={name}
          id={`id-${name}`}
          onFocus={this.handleFocus}
          onBlur={this.handleBlur}
          {...props}
        />
        {!!error && <span className="errorText">{error}</span>}
      </label>
    );
  }
}

Input.defaultProps = {
  type: 'text',
  error: '',
  required: false,
  autoComplete: 'off',
  labelClass: '',
  inputClass: '',
};

Input.propTypes = {
  value: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  placeholder: PropTypes.string.isRequired,
  error: PropTypes.string,
  type: PropTypes.string,
  required: PropTypes.bool,
  autoComplete: PropTypes.string,
  labelClass: PropTypes.string,
  inputClass: PropTypes.string,
};

export default Input;

Next, in the /components folder, create the file Form.jsx, in which the composition containing the form will be declared. All methods for working with it will be obtained through props, as well as the value for the intuitions, so the state is not needed here. We get:

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Input from './Input';
import FormWrapper from '../containers/FormWrapper';

class Form extends Component {
  render () {
    const {
      data: {username, email, phone},
      errors,
      handleInput,
      handleSubmit,
    } = this.props;
    return (
      <div className = "openBill">
        <form className = "openBillForm" onSubmit = {handleSubmit}>
          <Input
            key = "username"
            value = {username}
            name = "username"
            onChange = {handleInput}
            placeholder = "Login"
            error = {errors.username}
            required
          />
          <Input
            key = "phone"
            value = {phone}
            name = "phone"
            onChange = {handleInput}
            placeholder = "Phone"
            error = {errors.phone}
            required
          />
          <Input
            key = "email"
            value = {email}
            type = "email"
            name = "email"
            onChange = {handleInput}
            placeholder = "Email"
            error = {errors.email}
            required
          />
          <button type = "submit" className = "submitBtn">
            Submit form
          </ button>
        </ form>
      </ div>
    );
  }
}

Form.propTypes = {
  data: PropTypes.shape ({
    username: PropTypes.string.isRequired,
    phone: PropTypes.string.isRequired,
    email: PropTypes.string.isRequired,
  }). isRequired,
  errors: PropTypes.shape ({
    username: PropTypes.string.isRequired,
    phone: PropTypes.string.isRequired,
    email: PropTypes.string.isRequired,
  }). isRequired,
  handleInput: PropTypes.func.isRequired,
  handleSubmit: PropTypes.func.isRequired,
};};

export default FormWrapper (Form);

Creating an HOC

In the /containers folder, create the file FormWrapper.jsx. We declare a function that takes the WrappedComponent component as its argument and returns the WrappedForm class. The render method of this class returns a WrappedComponent with props integrated into it. Try to use the classic function declaration, as this will simplify the debugging process.

In the WrappedForm class, we create a state: isFetching. This will act as a flag to control asynchronous requests, data (an object with a value of the intuitions), and errors (an object for storing errors). The declared state will be passed to WrappedComponent. In this way, the storage of the form state storage is carried out to the upper level, which makes the code more readable and transparent.

export default function Wrapper(WrappedComponent) {
  return class FormWrapper extends Component {
    state = {
      isFetching: false,
      data: {
        username: '',
        phone: '',
        email: '',
      },
      errors: {
        username: '',
        phone: '',
        email: '',
      },
    };

    render() {
      return <WrappedComponent {...this.state} />;
    }
  };
}

But such an implementation is not universal, because for each form you will have to create your own wrapper. You can improve this system and put HOC inside one more function that will generate initial state values.

import React, { Component } from 'react';

export default function getDefaultValues(initialState, requiredFields) {
  return function Wrapper(WrappedComponent) {
    return class WrappedForm extends Component {
      state = {
        isFetching: false,
        data: initialState,
        errors: requiredFields,
      };

      render() {
        return <WrappedComponent {...this.state} {...this.props} />;
      }
    };
  };
}

In this function, you can pass not only the initial state values, but, generally, any parameters. For example, the attributes and methods on which you can create a form in Form.jsx. An example of such an implementation will be the topic for the next article.

In the Form.jsx file, declare the initial state values and pass them to the HOC:

const initialState = {
    username: '',
    phone: '',
    email: '',
};

export default FormWrapper(initialState, initialState)(Form);

Let's create a handleInput method for processing the values entered into the input. It receives an event, from which we take value and name and pass them to setState. Since the values of the intuitions are stored in the data object, in setState we call the function. Simultaneous to saving the received value, we zero the error store of the field being changed. We get:

handleInput = event => {
  const { value, name } = event.currentTarget;
  this.setState(({ data, errors }) => ({
    data: {
      ...data,
      [name]: value,
    },
    errors: {
      ...errors,
      [name]: '',
    },
  }));
};

Now create a handeSubmit method to process the form and output the data to the console, but, before that, you need to pass validation. We will only validate the required fields, that is, all the keys of this.state.errors. We get:

handleSubmit = e => {
    e.preventDefault();
    const { data } = this.state;
    const isValid = Object.keys(data).reduce(
        (sum, item) => sum && this.validate(item, data[item]),
        true
    );
    if (isValid) {
      console.log(data);
    }
};

Use the reduce method to list all the required fields. At each iteration, the validate method is invoked, to which we pass the name/value pair. Inside the method, the correctness of the entered data is checked, which results in the return of the boolean type. If at least one pair of values fails validation, the variable isValid will be false, and the data in the console will not be displayed, that is, the form will not be processed. Here we consider the simple case of checking for a nonempty form. Here's the validate method:

validate = (name, value) => {
     if (! value.trim ()) {
       this.setState (
         ({errors}) => ({
           errors: {
             ... errors,
             [name]: 'field must not be empty',
           },
         }),
         () => false
       );
     } else {
       return true;
     }
};};

Both the handleSubmit and the handleInput methods must be passed to WrappedComponent:

render() {
    return (
        <WrappedComponent
            {...this.state}
            {...this.props}
            handleInput={this.handleInput}
            handleSubmit={this.handleSubmit}
        />
    );
}

As a result, we get a ready-made feedback form, with simple validation and error reporting. In doing so, we have removed the logical part from the component responsible for the mapping.

Conclusion

So, we have considered a basic example of creating an HOC for form processing. When creating the form, only simple intuitions were used, without complex elements, such as drop-down lists, checkboxes, radio buttons, and others. If they are available, you may have to create additional methods for handling events.

Form (document)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Custom Validators in Quarkus
  • 10 Things to Know When Using SHACL With GraphDB
  • Isolating Noisy Neighbors in Distributed Systems: The Power of Shuffle-Sharding
  • Spring Boot vs Eclipse MicroProfile: Resident Set Size (RSS) and Time to First Request (TFR) Comparative

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: