martes, 21 de septiembre de 2021

React Form Validation 2 Parameterized Component

React Form Validation 2 Parameterized Component 

 
Create a new Component that contain all the elements in the group of Input, basically remove the div input element from app.js to be substituted with the new component DivInput, clean the imports not used anymore in app.js, and import these element imports to the new file component.
/components/DivInput.js
 
import React from "react";
import {LabelGroupInputInputIconValidationMsgInputErrorfrom "../elements/formulary.js";
import {faCheckCirclefrom "@fortawesome/free-solid-svg-icons";

const DivInput = () => {
  return (
    <div>
        <Label htmlFor="user">User</Label>
        <GroupInput>
          <Input type="text" placeholder="user" id="user"/>
          <IconValidation icon={faCheckCircle} />
        </GroupInput>
        <MsgInputError>This is on the react application</MsgInputError>
    </div>
  );
};

export default DivInput;
 
In app.js create many DivInput components
 
import React from "react";
import { Form_styledDivTermsDivErrorFormMsgDivSubmitCentered
ButtonPMsgSuccessfrom "./elements/formulary.js";
import DivInput from "./components/DivInput";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationTrianglefrom "@fortawesome/free-solid-svg-icons";

const App = () => {
  return (
    <main>
      <Form_styled action="">
        <DivInput/>
        <DivInput/>
        <DivInput/>
        <DivInput/>
        <DivInput/>
        
        <DivTerms>
          <label>
            <input type="checkbox" name='terms' id='terms'/>
            I Accept terms and conditions.
          </label>
        </DivTerms>
        {false && <DivErrorFormMsg>
          <p>
            <FontAwesomeIcon icon={faExclamationTriangle}/>
            <b>Error</b>Please, Check your data and accept terms.
          </p>
        </DivErrorFormMsg>}
        <DivSubmitCentered>
          <Button type="submit" >Submit</Button>
          <PMsgSuccess>Data send successfully!</PMsgSuccess>
        </DivSubmitCentered>
      </Form_styled>
    </main>
  );
};

 

 We need parameterize the component in order to allow us change the label, placeholder, type, id, script of validation...

components/DivInput.js
import React from "react";
import {LabelGroupInputInputIconValidationMsgInputErrorfrom "../elements/formulary.js";
import {faCheckCirclefrom "@fortawesome/free-solid-svg-icons";

const DivInput = ({typelabelplaceholdernamemsginputerrorregexp}) => {
  return (
    <div>
        <Label htmlFor={name}>{label}</Label>
        <GroupInput>
          <Input type={type} placeholder={placeholder} id={name}/>
          <IconValidation icon={faCheckCircle} />
        </GroupInput>
        <MsgInputError>{msginputerror}</MsgInputError>
    </div>
  );
};

export default DivInput;
 
app.js
... 
  return (
    <main>
      <FormStyled action="">
        <DivInput type="text" label="User" placeholder="type the user" name="user" 
        msginputerror="The user must have between 4 and 16 digits of numbers, leters and '_'." regexp=""/>
        <DivInput type="text" label="Name" placeholder="type the name" name="name" 
        msginputerror="The name must have between 1 and 40 digits of letters, spaces and accents." regexp=""/>
        <DivInput type="password" label="Password" placeholder="type the password" name="password" 
        msginputerror="The password must have between 4 and 12 digits.." regexp=""/>
        <DivInput type="password" label="Repeat Password" placeholder="retype the password" name="password2" 
        msginputerror="message Both passwords must be the same. error" regexp=""/>
        <DivInput type="email" label="email" placeholder="type the email" name="email" 
        msginputerror="message The email must correspont to an email syntax." regexp=""/>
        <DivInput type="text" label="Telephone" placeholder="type the telephone" name="telephone" 
        msginputerror="message The telephone must have between 7 and 14 digits of numbers. error" regexp=""/>
        
        <DivTerms>
          <label>
            <input type="checkbox" name='terms' id='terms'/>
            I Accept terms and conditions.
          </label>
        </DivTerms>
        {false && <DivErrorFormMsg>
          <p>
            <FontAwesomeIcon icon={faExclamationTriangle}/>
            <b>Error</b>Please, Check your data and accept terms.
          </p>
        </DivErrorFormMsg>}
...
 
 

To manage the data of the fields, the strategy is use de state at level of app.js to store the content of the fields and the valid state of each of them.

app.js
 
import React, { useState } from "react";

... 
const App = () => {
  const [userchangeUser] = useState({field:''valid: null});
  const [namechangeName] = useState({field:''valid: null});
  const [passwordchangePassword] = useState({field:''valid: null});
  const [password2changePassword2] = useState({field:''valid: null});
  const [emailchangeEmail] = useState({field:''valid: null});
  const [telephonechangeTelephone] = useState({field:''valid: null});
    
  return (
    <main>
      <FormStyled action="">
... 
 
This state and changeState needs to be passed to the DivInput component, use the props approach.
 
app.js
... 
      <FormStyled action="">
        <DivInput state={user} changeState={changeUser} type="text" label="User" placeholder="type the user" name="user" 
        msginputerror="The user must have between 4 and 16 digits of numbers, leters and '_'." regexp=""/>
        <DivInput state={name} changeState={changeName} type="text" label="Name" placeholder="type the name" name="name" 
        msginputerror="The name must have between 1 and 40 digits of letters, spaces and accents." regexp=""/>
        <DivInput state={password} changeState={changePassword} type="password" label="Password" placeholder="type the password" name="password" 
...
 
These props are received in the component and updated the field value by the onChange method.
the state is using the spread operator (...state) to take all the previos properties of the state and not lost it.The spread operator takes eachone of the elements of state and put to the new state without specifying manually each asignation.
 
DivInput.js component
const DivInput = ({statechangeStatetypelabelplaceholdernamemsginputerrorregexp}) => {
  const onChange = (e=> {
    changeState({...statefield: e.target.value});
  };

  return (
    <div>
        <Label htmlFor={name}>{label}</Label>
        <GroupInput>
          <Input type={type} placeholder={placeholder} id={name} value={state.field} 
          onChange={onChange}/>
          <IconValidation icon={faCheckCircle} />
        </GroupInput>
        <MsgInputError>{msginputerror}</MsgInputError>
    </div>
  );
};

 
To make the validation of the fields, the event that activate the procedure validation, will be the onKeyUp and onBlur. also the state needs to be passed to manage the icon and colour for the label.

DivInput.js
import React from "react";
import {LabelGroupInputInputIconValidationMsgInputErrorfrom "../elements/formulary.js";
import {faCheckCirclefaTimesCirclefrom "@fortawesome/free-solid-svg-icons";

const DivInput = ({statechangeStatetypelabelplaceholdernamemsginputerrorregexp}) => {
  const onChange = (e=> {
    changeState({...statefield: e.target.value});
  };

  const validation = () => {
    if(regexp){
      if(regexp.test(state.field)){
        changeState({...statevalid: 'true'});
      } else {
        changeState({...statevalid: 'false'});
      }
    }
  };

  return (
    <div>
        <Label htmlFor={name} valid={state.valid}>{label}</Label>
        <GroupInput>
          <Input type={type} placeholder={placeholder} id={name} value={state.field} 
          onChange={onChange} onKeyUp={validation} onBlur={validation} valid={state.valid}/>
          <IconValidation icon={state.valid === 'true' ? faCheckCircle : faTimesCircle} 
          valid={state.valid} />
        </GroupInput>
        <MsgInputError valid={state.valid}>{msginputerror}</MsgInputError>
    </div>
  );
};

export default DivInput;
 
 In order to change the styles, the css receive the state by the props

... 
import styled, {cssfrom "styled-components";
... 
const Input = styled.input`
  width: 100%;
  height: 45px;
  line-height: 45px;
  border-radius: 5px;
  background: white;
  padding: 0 40px 0 10px;
  transition: 0.3s ease all;
  border: 3px solid transparent;
  
  &:focus {
      border: 5px solid ${colors.border};
      outline: none;
      box-shadow: 3px 3px 5px rgba( 163,163, 163, 0.4);
  }

  ${props => props.valid === 'true' && css`
      border: 3px solid transparent;
  `}

  ${props => props.valid === 'false' && css`
      border: 3px solid ${colors.error} !important;
  `}
`;
...
 
The !important overrides the :focus rule allowing apply the change at the moment.
 

Passing regular expression to validate, and another function to validate the second password.

app.js

...
const expressions = {
    user: /^[a-zA-Z0-9_-]{4,16}$/,    // letters, numbers, _ , -
    name: /^[a-zA-ZÀ-ÿ\s]{1,40}$/,      // letters, spaces, accents
    password: /^.{4,12}$/,              // 4 and 12 digits
    email: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-.]+$/,
    telephone: /^\d{7,14}$/             // 7 to 14 numbers
}

const App = () => {
  const [userchangeUser] = useState({field:''valid: null});
...
 
 
The input fields have all the props defined and passed to the DivInput Component and for the password2 input field pass a function that validate correlating with password field.
app.js
return (
    <main>
      <FormStyled action="">
        <DivInput state={user} changeState={changeUser} type="text" label="User" 
        placeholder="type the user" name="user" 
        msginputerror="The user must have between 4 and 16 digits of numbers, leters and '_'." 
        regexp={expressions.user}/>
        <DivInput state={name} changeState={changeName} type="text" label="Name" 
        placeholder="type the name" name="name" 
        msginputerror="The name must have between 1 and 40 digits of letters, spaces and accents." 
        regexp={expressions.name}/>
        <DivInput state={password} changeState={changePassword} type="password" label="Password" 
        placeholder="type the password" name="password" 
        msginputerror="The password must have between 4 and 12 digits.."
        regexp={expressions.password}/>
        <DivInput state={password2} changeState={changePassword2} type="password" label="Repeat Password"
        placeholder="retype the password" name="password2" 
        msginputerror="message Both passwords must be the same. error"
        functionValPass={validPassword2}/>
        <DivInput state={email} changeState={changeEmail} type="email" label="email" 
        placeholder="type the email" name="email" 
        msginputerror="message The email must correspont to an email syntax." 
        regexp={expressions.email}/>
        <DivInput state={telephone} changeState={changeTelephone} type="text" label="Telephone" 
        placeholder="type the telephone" name="telephone" 
        msginputerror="message The telephone must have between 7 and 14 digits of numbers. error" 
        regexp={expressions.telephone}/>
        
  
 
Notice that the changePassword2 function uses the prevState to update the state of password2, that because otherwise the actual value will be undefined because of lost of state. 

 
validPassword2 in app.js
const validPassword2 = () => {
    ifpassword.field.length > 0){
      if (password.field !== password2.field){
        changePassword2( (prevState=> {
          return {...prevStatevalid: 'false'}
        });
      } else {
        changePassword2( (prevState=> {
          return {...prevStatevalid: 'true'}
        });
      }
    }
  }; 
  
 
At the moment we have validated the input fields
 

 To manage the Terms, add the terms state and include the onChange function and add to the props
app.js
const [termschangeTerms] = useState(false);
... 
 
  const onChangeTerms = (e=> {
    changeTerms(e.target.checked);
  };

...
        <DivTerms>
          <label>
            <input state={terms} checked={terms} onChange={onChangeTerms} 
            type="checkbox" name='terms' id='terms'/>
            I Accept terms and conditions.
          </label>
        </DivTerms>
...
 
 To validate the Form create another state validForm initialized to null, on the Form add the onSubmit property with a function onSubmit. The onSubmit function deactivate the submit with event.preventDefault() in order to allow do the validation of the fields. in the onSubmit function if all the fields are valid then change the validForm state to false or true.

app.js
import React, { useState } from "react";
import { FormStyledDivTermsDivErrorFormMsgDivSubmitCentered
ButtonPMsgSuccessfrom "./elements/formulary.js";
import DivInput from "./components/DivInput";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationTrianglefrom "@fortawesome/free-solid-svg-icons";

const expressions = {
    user: /^[a-zA-Z0-9_-]{4,16}$/,    // letters, numbers, _ , -
    name: /^[a-zA-ZÀ-ÿ\s]{1,40}$/,      // letters, spaces, accents
    password: /^.{4,12}$/,              // 4 and 12 digits
    email: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-.]+$/,
    telephone: /^\d{7,14}$/             // 7 to 14 numbers
}

const App = () => {
  const [userchangeUser] = useState({field:''valid: null});
  const [namechangeName] = useState({field:''valid: null});
  const [passwordchangePassword] = useState({field:''valid: null});
  const [password2changePassword2] = useState({field:''valid: null});
  const [emailchangeEmail] = useState({field:''valid: null});
  const [telephonechangeTelephone] = useState({field:''valid: null});
  const [termschangeTerms] = useState(false);
  const [validFormchangeValidForm] = useState(null);
    
  const validPassword2 = (state=> {
    ifpassword.field.length > 0){
      if (password.field !== password2.field){ 
        changePassword2( (preState=> {
          return {...preStatevalid:'false'}
        });
      } else {
        changePassword2( (preState=> {
          return {...preStatevalid:'true'}
        });
      }
    }
  }; 
  
  const onChangeTerms = (e=> {
    changeTerms(e.target.checked);
  };

  const onSubmit = (e=> {
    e.preventDefault();
    
    if(user.valid === 'true' && name.valid === 'true' && password.valid === 'true' 
       && password2.valid === 'true' && email.valid === 'true' && telephone.valid === 'true'
       && terms){
         changeValidForm(true);
         changeUser({field:''valid: null});
         changeName({field:''valid: null});
         changePassword({field:''valid: null});
         changePassword2({field:''valid: null});
         changeEmail({field:''valid: null});
         changeTelephone({field:''valid: null});
         changeTerms(false);

         // .... Do the next process with the Form Data

         setTimeout(() =>changeValidForm(null);} ,5000);     
       } else {
         changeValidForm(false);
       }
  }

  return (
    <main>
      <FormStyled action="" onSubmit={onSubmit}>
        <DivInput state={user} changeState={changeUser} type="text" label="User" 
        placeholder="type the user" name="user" 
        msginputerror="The user must have between 4 and 16 digits of numbers, leters and '_'." 
        regexp={expressions.user}/>
        <DivInput state={name} changeState={changeName} type="text" label="Name" 
        placeholder="type the name" name="name" 
        msginputerror="The name must have between 1 and 40 digits of letters, spaces and accents." 
        regexp={expressions.name}/>
        <DivInput state={password} changeState={changePassword} type="password" label="Password" 
        placeholder="type the password" name="password" 
        msginputerror="The password must have between 4 and 12 digits.."
        regexp={expressions.password}/>
        <DivInput state={password2} changeState={changePassword2} type="password" label="Repeat Password"
        placeholder="retype the password" name="password2" 
        msginputerror="message Both passwords must be the same. error"
        functionValPass={validPassword2}/>
        <DivInput state={email} changeState={changeEmail} type="email" label="email" 
        placeholder="type the email" name="email" 
        msginputerror="message The email must correspont to an email syntax." 
        regexp={expressions.email}/>
        <DivInput state={telephone} changeState={changeTelephone} type="text" label="Telephone" 
        placeholder="type the telephone" name="telephone" 
        msginputerror="message The telephone must have between 7 and 14 digits of numbers. error" 
        regexp={expressions.telephone}/>
        
        <DivTerms>
          <label>
            <input state={terms} checked={terms} onChange={onChangeTerms} 
            type="checkbox" name='terms' id='terms'/>
            I Accept terms and conditions.
          </label>
        </DivTerms>
        {validForm === false && <DivErrorFormMsg>
          <p>
            <FontAwesomeIcon icon={faExclamationTriangle}/>
            <b>Error</b>Please, Check your data and accept terms.
          </p>
        </DivErrorFormMsg>}
        <DivSubmitCentered>
          <Button type="submit" >Submit</Button>
          {!validForm === false && <PMsgSuccess>Data send successfully!</PMsgSuccess>}
        </DivSubmitCentered>
      </FormStyled>
    </main>
  );
};

export default App;


DivInput.js component
import React from "react";
import {LabelGroupInputInputIconValidationMsgInputErrorfrom "../elements/formulary.js";
import {faCheckCirclefaTimesCirclefrom "@fortawesome/free-solid-svg-icons";

const DivInput = ({statechangeStatetypelabelplaceholdernamemsginputerror
  regexpfunctionValPass}) => {
  const onChange = (e=> {
    changeState({...statefield: e.target.value});
  };

  const validation = () => {
    if(regexp){
      if(regexp.test(state.field)){
        changeState({...statevalid: 'true'});
      } else {
        changeState({...statevalid: 'false'});
      }
    }

    if(functionValPass){
      functionValPass();
    }
  };

  return (
    <div>
        <Label htmlFor={name} valid={state.valid}>{label}</Label>
        <GroupInput>
          <Input type={type} placeholder={placeholder} id={name} value={state.field} 
          onChange={onChange} onKeyUp={validation} onBlur={validation} valid={state.valid}/>
          <IconValidation icon={state.valid === 'true' ? faCheckCircle : faTimesCircle} 
          valid={state.valid} />
        </GroupInput>
        <MsgInputError valid={state.valid}>{msginputerror}</MsgInputError>
    </div>
  );
};

export default DivInput;

formulary.js

import styled, {cssfrom "styled-components";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

const colors = {
  border: "#0075FF",
  buttonover: "#1155FF",
  error: "#EE5533",
  success: "#119200",
};
const FormStyled = styled.form`
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;

  @media (max-width: 800px) {
    grid-template-columns: 1fr;
  }
`;

const Label = styled.label`
  display: block;
  font-weight: 700;
  padding: 10px;
  min-height: 40px;
  cursor: pointer;

  ${props => props.valid === 'false' && css`
     color: ${colors.error}
  `}
`;

const GroupInput = styled.div`
  position: relative;
  z-index: 90;
`;

const Input = styled.input`
  width: 100%;
  height: 45px;
  line-height: 45px;
  border-radius: 5px;
  background: white;
  padding: 0 40px 0 10px;
  transition: 0.3s ease all;
  border: 3px solid transparent;
  
  &:focus {
      border: 5px solid ${colors.border};
      outline: none;
      box-shadow: 3px 3px 5px rgba( 163,163, 163, 0.4);
  }

  ${props => props.valid === 'true' && css`
      border: 3px solid transparent;
  `}

  ${props => props.valid === 'false' && css`
      border: 3px solid ${colors.error} !important;
  `}
`;

const MsgInputError = styled.p`
  font-size: 12px;
  color: ${colors.error};
  margin-bottom: 0;
  display: none;

  ${props => props.valid === 'false' && css`
    display: block;
  `}
`;

const IconValidation = styled(FontAwesomeIcon)`
  position: absolute;
  right: 10px;
  bottom: 14px;
  z-index: 100;
  font-size: 16px;
  opacity: 0; 

  ${props => props.valid === 'false' && css`
    opacity: 1;
    color: ${colors.error}
  `}
  ${props => props.valid === 'true' && css`
    opacity: 1;
    color: ${colors.success}
  `}
`;

const DivTerms = styled.div`
  grid-column: span 2;

  input {
      margin-right: 10px;
  }

  @media (max-width: 800px){
      grid-column: span 1;
  }
`;

const DivErrorFormMsg = styled.div`
  grid-column: span 2;
  height: 45px;
  line-height: 45px;
  background: ${colors.error};
  padding: 0 15px;
  border-radius: 3px; 
  p {
      margin: 0;
  }
  b{
      margin-left: 10px;
  }
`;

const DivSubmitCentered = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  grid-column: span 2;

    @media (max-width: 800px){
      grid-column: span 1;
  }
`;

const Button = styled.button`
    height: 45px;
    line-height: 45px;
    width: 30%;
    background: #0075ff;
    color: white;
    font-weight: bold;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: .1s ease all;

    &:hover {
       box-shadow: 5px 5px 5px rgba(163,163,163, 1);
       background: ${colors.buttonover};
    }
`;

const PMsgSuccess = styled.p`
    font-size: 14px;
    color: ${colors.success};
    display: block;
`;

export { FormStyledLabelGroupInputInputMsgInputErrorIconValidation,
         DivTermsDivErrorFormMsgDivSubmitCenteredButtonPMsgSuccess};

 


 
eot