1

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,

  • Both fields should accept only text
  • First name is required and should have at least 4 characters.
  • If Last name field has value, then it needs to be at least 3 characters.

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)

10
  • 1
    All your validation rules can be done with HTML alone. Check the required, minlength and pattern attributes. Commented Apr 22, 2021 at 2:25
  • @Evert, For now to make it clear, I have made these validation rules but in real time I would have more validation.. I am insisted to only do the validation in react's javascript part because need to change .. Commented Apr 22, 2021 at 2:30
  • When do you want field validation to occur? Upon every change? Upon field onBlur? Should intermediate validations prevent your context/state from updating? Commented Apr 22, 2021 at 4:13
  • Hopefully people called Tom or Amy don't want to use your system... Commented Apr 22, 2021 at 4:13
  • @DrewReese, Yes the validation needs to happen upon input change i.e,(input dirty) .. And yes intermediate validation can prevent the context/state from updating.. Also shall we move the <input> as a separate component bro because I feel I am repeating things here.. Commented Apr 22, 2021 at 4:19

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks for your quick solution and the effort you are making is much appreciable bro.. And you have clearly provided the solution that I am in the need.. Bro again sorry for not mentioning other one validation which you have asked in comments.. I am also need to add on blur validation bro.. I am really sorry for not mentioing that.. I have been in the need to implement both on dirty and on blur validation..
Also if you have some time the could you please kindly help me in making the inputs as a separate component? (Just asking as a request bro) ..
@Undefined Since you are validating the inputs on every change, I'm not sure onBlur validation will do much for you, the inputs will have already been validated from the last change event before the field can be unfocused. I also recommend not re-inventing the wheel if you can, many form field validation libraries exist specifically for the features you ask about. What sort of factorization of the inputs are you looking for? Just a simple componentization to make the code a little more DRY?
Thanks Bro.. My lead has asked me not to include any third party library and asks me to achieve it in pure react way and that is why I am seeking for help.. Also he said that he should not see any repetition of code for the inputs so he wants me to make that as a separate component and need to pass down the required props to that component..
I don't know whether I am making you confused.. But your help as for as now are really much more appreciable bro..
|
0

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}
  ));
}

5 Comments

No I cannot set the value like this in useState,. I am getting the values from context api.. What would happen if the form is having multiple inputs say like 50 inputs in this component.. Should we need to include 50 useState hook?
Based on your comment in solution not sure where it is in your folder structure the utils is under components/utils.js ..
I have updated the comment. The main point of React is creating re-usable components with DRY principle. You will create your own input component that has a validation already integrated along with other dynamic props then use that one as Input component.
ok, made some more changes to show how you can use that one with an array of data. you can either pass data and multiply this component as many times as you wish with different props. any prop other than value will be passed to input and by default they will be controlled and validated also will populate error text underneath when validation fails.
sorry there was a conflict with props.value and setState value. replaced that one with inputValue instead.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.