Undefined
Undefined

Reputation: 1021

How to add input validation in react?

I am having a simple form that has firstName and lastName.

    <label htmlFor="firstName">First Name: </label>
    <input
      type="text"
      className="form-control"
      id="firstName"
      name="firstName"
      value={basicDetails.firstName}
      onChange={(event) => handleInputChange(event)}
    />

    <label htmlFor="lastName">Last Name: </label>
    <input
      type="text"
      className="form-control"
      id="lastName"
      name="lastName"
      value={basicDetails.lastName}
      onChange={(event) => handleInputChange(event)}
    />

For this I am trying to add validation.

The validation rules are,

Things I have tried to achieve this,

components/utils.js

export function isLettersOnly(string) {
  return /^[a-zA-Z]+$/.test(string);
}

components/basic_details.js

  const handleInputChange = (event) => {
    const { name, value } = event.target;

    if (!isLettersOnly(value)) {
      return;
    }

    setValue((prev) => {
      const basicDetails = { ...prev.basicDetails, [name]: value };
      return { ...prev, basicDetails };
    });
  };

On handle input field, I am making the validation to check whether the input has value but I am unable to get the point how to catch the actual validation error and display below respective input box.

Kindly please help me to display the validation message on the respective fields.

Working example:

Edit next-dynamic-testing-issue (forked)

Upvotes: 1

Views: 1462

Answers (2)

Drew Reese
Drew Reese

Reputation: 202608

I suggest adding an errors property to the form data in form_context:

const [formValue, setFormValue] = useState({
  basicDetails: {
    firstName: '',
    lastName: '',
    profileSummary: '',
    errors: {},
  },
  ...
});

Add the validation to basic_details subform:

const ErrorText = ({ children }) => (
  <div style={{ color: 'red' }}>{children}</div>
);

const BasicDetails = () => {
  const [value, setValue] = React.useContext(FormContext);
  const { basicDetails } = value;

  const handleInputChange = (event) => {
    const { name, value } = event.target;

    if (!isLettersOnly(value)) {
      setValue((value) => ({
        ...value,
        basicDetails: {
          ...value.basicDetails,
          errors: {
            ...value.basicDetails.errors,
            [name]: 'Can have only letters.',
          },
        },
      }));
      return;
    }

    switch (name) {
      case 'firstName': {
        const error = value.length < 4 ? 'Length must be at least 4.' : null;
        setValue((value) => ({
          ...value,
          basicDetails: {
            ...value.basicDetails,
            errors: {
              ...value.basicDetails.errors,
              [name]: error,
            },
          },
        }));
        break;
      }

      case 'lastName': {
        const error = value.length < 3 ? 'Length must be at least 3.' : null;
        setValue((value) => ({
          ...value,
          basicDetails: {
            ...value.basicDetails,
            errors: {
              ...value.basicDetails.errors,
              [name]: error,
            },
          },
        }));
        break;
      }

      default:
      // ignore
    }

    setValue((prev) => {
      const basicDetails = { ...prev.basicDetails, [name]: value };
      return { ...prev, basicDetails };
    });
  };

  return (
    <>
      <br />
      <br />
      <div className="form-group col-sm-6">
        <label htmlFor="firstName">First Name: </label>
        <input
          type="text"
          className="form-control"
          id="firstName"
          name="firstName"
          value={basicDetails.firstName}
          onChange={(event) => handleInputChange(event)}
        />
      </div>
      <br />
      {basicDetails.errors.firstName && (
        <ErrorText>{basicDetails.errors.firstName}</ErrorText>
      )}
      <br />
      <br />
      <div className="form-group col-sm-4">
        <label htmlFor="lastName">Last Name: </label>
        <input
          type="text"
          className="form-control"
          id="lastName"
          name="lastName"
          value={basicDetails.lastName}
          onChange={(event) => handleInputChange(event)}
        />
      </div>
      <br />
      {basicDetails.errors.lastName && (
        <ErrorText>{basicDetails.errors.lastName}</ErrorText>
      )}
      <br />
    </>
  );
};

Lastly, check the field values and errors to set the disabled attribute on the next button in index.js. The first !(value.basicDetails.firstName && value.basicDetails.lastName) condition handles the initial/empty values state while the second condition handles the error values.

{currentPage === 1 && (
  <>
    <BasicDetails />
    <button
      disabled={
        !(
          value.basicDetails.firstName && value.basicDetails.lastName
        ) ||
        Object.values(value.basicDetails.errors).filter(Boolean).length
      }
      onClick={next}
    >
      Next
    </button>
  </>
)}

This pattern can be repeated for the following steps.

Edit how-to-add-input-validation-in-react

Upvotes: 1

serdar.sanri
serdar.sanri

Reputation: 2228

First you must be getting converting controlled component to uncontrolled component error in your console. For controlled component it is always preferred to use state to set value for the input. And with onChange handler you set the state. I will try to put into a single component so you would get the idea and apply your case

import React, {useState}  from 'react';
import {isLettersOnly} from './components/utils'; // not sure where it is in your folder structure

const MyInputComponent = ({value, ...props}) => {
   const [inputValue, setInputValue] = useState(value || ''); // input value should be empty string or undefined. null will not be accepted.
   const [error, setError] = useState(null);


   const handleChange = event => {
      
    const { name, value } = event.target;

     if (!isLettersOnly(value)) {
         setError('Invalid Input');
     }
      setInputValue(value);
     
   }

   return (
    <>
     <input
      value={inputValue}
      onChange={handleChange}
      {...props}
    />
    {error && (
        <span className={"error"}>{error}</span>
    )}
    </>
   )
}

export default MyInputComponent;

This is a very rudimentary component. just to show the concept. You can then import this component as your input field and pass necessary props like name, className etc from parent.

import React from 'react';
import MyInputComponent from 'components/MyInputComponent';

const MyForm = (props) => {
  return props.data && props.data.map(data=> (
    <MyInputComponent
      name="lastName"
      className="form-control"
      value={data.lastName}
  ));
}

Upvotes: 0

Related Questions