0

For Form Validation, Instead of using a package I tried to build one myself. So No matter how many fields you have, the validation gets added automatically. But there is one MINOR issue.

Since the set state is async, I'm only able to get the last typed value after typing one more lettter.

For Example : If I need to show an error "Password must contain atleast 6 characters" , This error gets displayed. But it only goes away if you type the 7th character. Same with other fields aswell.

I am checking the validation on the onChangeText and onBlur , I also tried with onKeyPress.

Any solution is apreciated.

Here is the code: I have removed the styles etc. So this code should work for you out of the box.

import React, { useState, useRef } from 'react';
import { Text, View, TextInput } from 'react-native';

const Signup = ({}) => {
   const inputRef = useRef([]);
   const [inputs, setInputs] = useState([
      { id: 0, placeholder: 'First Name', name: 'firstName', type: 'text', hasError: false, errMessage: '' },
      { id: 1, placeholder: 'Last Name', name: 'lastName', type: 'text', hasError: false, errMessage: '' },
      { id: 2, placeholder: 'Email', name: 'email', type: 'email', hasError: false, errMessage: '' },
      { id: 3, placeholder: 'Password', name: 'password', type: 'password', mhasError: false, errMessage: '' },
      {
         id: 4,
         placeholder: 'Confirm Password',
         name: 'confirmPassword',
         type: 'password',
         hasError: false,
         errMessage: '',
      },
   ]);

   const [fields, setFields] = useState({
      firstName: '',
      lastName: '',
      email: '',
      password: '',
      confirmPassword: '',
   });

   const handleChange = (text, fieldName, id, type) => {
      setFields(currentValue => ({
         ...currentValue,
         [fieldName]: text,
      }));
      validateForm(fieldName, id, type);
   };

   const validateForm = (fieldName, id, type) => {
      const ID = id;
      const newInput = [...inputs];

      if (type === 'text') {
         if (fields[fieldName].length === 0 || fields[fieldName] === '') {
            newInput[ID].errMessage = 'Please fill the field';
            newInput[ID].hasError = true;
         } else if (fields[fieldName].length < 3) {
            newInput[ID].hasError = true;
            newInput[ID].errMessage = 'Must be more than 2 letters';
         } else {
            newInput[ID].errMessage = '';
            newInput[ID].hasError = false;
         }
      }
      if (type === 'email') {
         if (fields[fieldName].length === 0 || fields[fieldName] === '') {
            newInput[ID].errMessage = 'Please fill the field';
            newInput[ID].hasError = true;
         } else if (
            !fields[fieldName].match(
               /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
            )
         ) {
            newInput[ID].hasError = true;
            newInput[ID].errMessage = 'Invalid Email';
         } else {
            newInput[ID].errMessage = '';
            newInput[ID].hasError = false;
         }
      }
      if (type === 'password' && fieldName === 'password') {
         if (fields[fieldName].length === 0 || fields[fieldName] === '') {
            newInput[ID].errMessage = 'Please fill the field';
            newInput[ID].hasError = true;
         } else if (fields[fieldName].length < 6) {
            newInput[ID].hasError = true;
            newInput[ID].errMessage = 'Your password must be at least 6 characters';
         } else if (fields[fieldName].search(/[a-z]/i) < 0) {
            newInput[ID].hasError = true;
            newInput[ID].errMessage = 'Your password must contain at least one letter.';
         } else if (fields[fieldName].search(/[0-9]/) < 0) {
            newInput[ID].hasError = true;
            newInput[ID].errMessage = 'Your password must contain at least one digit.';
         } else {
            newInput[ID].errMessage = '';
            newInput[ID].hasError = false;
         }
      }
      if (type === 'password' && fieldName === 'confirmPassword') {
         if (fields['confirmPassword'] !== fields['password']) {
            newInput[ID].errMessage = "Password doesn't match";
            newInput[ID].hasError = true;
         } else {
            newInput[ID].errMessage = '';
            newInput[ID].hasError = false;
         }
      }
      setInputs(newInput);
   };

   return (
      <View style={{ paddingTop: 90 }}>
         <View>
            {inputs &&
               inputs.map((value, index) => (
                  <View key={index}>
                     <TextInput
                        onChangeText={text => {
                           handleChange(text, value.name, value.id, value.type);
                        }}
                        onBlur={() => validateForm(value.name, value.id, value.type)}
                        placeholder={value.placeholder}
                        ref={input => (inputRef.current[value.id] = input)}
                     />
                     {value.hasError && <Text style={{ color: 'red', marginTop: 3 }}>{value.errMessage}</Text>}
                  </View>
               ))}
         </View>
      </View>
   );
};

export default Signup;
1
  • I didn't understand what exactly you want. when do you want to show errors? Commented Feb 20, 2021 at 8:02

1 Answer 1

1

you are trying to validate the form in a synchronous way but as you mentioned, the setState is asynchronous.

you are using React Functional Component then you should think about The principle of reactivity and use hooks in a proper way.

It means first you should set the fields values in the handler and then listen to the fields values change using useEffect and then validate the form:

// input handler. set field values
const handleChange = (text, fieldName, id, type) => {
  setFields(currentValue => ({
     ...currentValue,
     [fieldName]: text,
  }));
};

// new form validator that doesn't need arguments and should check all fields
// errors and set inputs state when one ore more fields have errors
const validateForm = () => {
  // check all inputs error here and then
  // setInputs(...)
};

// the hook part. it runs every time the filds values changes
useEffect(() => {
  validateForm();
}, [fields]);

if you don't want to change your form validator function you can define new useState and keep the latest changed field's id in it and change this state when a field changed then run useEffect:

const [latestChangedFieldId, setLatestChangedFieldId] = useState('');

// input handler. set field values and change the latest field changed key
const handleChange = (text, fieldName, id, type) => {
  setFields(currentValue => ({
     ...currentValue,
     [fieldName]: text,
  }));

  setLatestChangedFieldId(id)
};

// the hook part. it runs every time the filds values changes
useEffect(() => {
  const currentChangedField = inputs.find((input) => input.id === latestChangedFieldId)

  validateForm(currentChangedField.name, currentChangedField.id, currentChangedField.type);
}, [latestChangedFieldId]);

but you really don't need to get name, id, and type in form validator function with you can track current changing field with only its id property

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

1 Comment

Thanks man, I was trying to solve this in one way and forgot I had many other options like this too

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.