Employee Validation

  • + 0 comments

    A much simpler implementation without useEffect. This is using useReducer which is much preferred in cases like creating forms.

    import React from "react";
    
    const ACTIONS = {
      UPDATE_FIELD: 'updateField',
      RESET_FORM: 'resetForm',
    }
    
    const FORM_FIELDS = {
      USERNAME: 'username',
      EMAIL: 'email',
      EMPLOYEE_ID: 'employeeId',
      JOINED_DATE: 'joinedDate',
    }
    
    const initialArgs = {
      username: '',
      email: '',
      employeeId: '',
      joinedDate: '',
      errors: {
        username: 'initial',
        email: 'initial',
        employeeId: 'initial',
        joinedDate: 'initial',
      },
    }
    
    // getting current year
    const getCurrentDate = () => {
      const date = new Date()
    
      return date.getFullYear()
    }
    
    const validateInput = (formField, value) => {
      switch(formField) {
        case FORM_FIELDS.USERNAME: {
          const alphabetSpacesRegex = /^[A-Za-z\s]*$/
          if (alphabetSpacesRegex.test(value) && value.length > 3) {
            return ''
          } else {
            return 'Name must be at least 4 characters long and only contain letters and spaces.'
          }
        }
        case FORM_FIELDS.EMAIL: {
          const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    
          if (emailRegex.test(value)) {
            return ''
          } else {
            return 'Email must be a valid email address'
          }
        }
        case FORM_FIELDS.EMPLOYEE_ID: {
          if (typeof value === "number" && `${value}`.length === 6) {
            return ''
          } else {
            return 'Employee ID must be exactly 6 digits.'
          }
        }
        case FORM_FIELDS.JOINED_DATE: {
          const currentYear = getCurrentDate()
          const [yearValue, _m, _d] = value.split("-")
    
          if (parseInt(yearValue) < parseInt(currentYear, 10)) {
            return ''
          } else {
            return 'Joining Date cannot be in the future'
          }
          
        }
    
        default: {
          return ''
        }
      }
    }
    
    const reducer = (state, action) => {
      try {
        if (!Object.hasOwn(action, 'type')) {
          throw Error('"type" not found');
        }
    
        switch(action.type) {
          case ACTIONS.UPDATE_FIELD: {
            // Expecting action to have field and value prop
            if (!Object.hasOwn(action, 'field')) {
              throw Error('"field" is required to update a form field')
            }
    
            const error = validateInput(action.field, action.value)
    
            return {
              ...state,
              [action.field]: action?.value || "",
              errors: {
                ...state.errors,
                [action.field]: error
              },
              formStates: {
                ...state.formStates,
                isDirty: true
              }
            }
          }
    
          case ACTIONS.RESET_FORM: {
            return initialArgs
          }
    
          default: {
            throw Error('Unknown action.');
          }
        }
      } catch(e) {
        console.log(`ERROR: ${e}`)
        return state
      }
      
    }
    
    const isAllInputValid = (state) => {
      try {
        const inputFieldsErrors = Object.values(state.errors)
    
        return inputFieldsErrors.every(value => !value)
      } catch(e) {
        console.log("Validation: Type ERROR")
      }
      
    }
    
    
    function EmployeeValidationForm() {
      const [employeeForm, dispatch] = React.useReducer(reducer, initialArgs)
      const isFormValid = isAllInputValid(employeeForm)
    
      const updateInput = (formField, event, transformInput) => {
        let inputValue = event.target.value
        if (transformInput) {
          inputValue = transformInput(inputValue)
        }
        dispatch({type: ACTIONS.UPDATE_FIELD, field: formField, value: inputValue})
      }
    
      return (
        <div className="layout-column align-items-center mt-20 ">
          <div className="layout-column align-items-start mb-10 w-50" data-testid="input-name">
            <input
              className="w-100"
              type="text"
              name="name"
              value={employeeForm.username}
              onChange={(e) => updateInput(FORM_FIELDS.USERNAME, e)}
              placeholder="Name"
              data-testid="input-name-test"
            />
            {
              employeeForm.errors.username ? <p className="error mt-2">
                Name must be at least 4 characters long and only contain letters and spaces
              </p> : null
            }
    
          </div>
          <div className="layout-column align-items-start mb-10 w-50" data-testid="input-email">
            <input
              className="w-100"
              type="text"
              name="email"
              value={employeeForm.email}
              onChange={(e) => updateInput(FORM_FIELDS.EMAIL, e)}
              placeholder="Email"
            />
            {employeeForm.errors.email ? <p className="error mt-2">Email must be a valid email address</p> : null}
            
          </div>
          <div className="layout-column align-items-start mb-10 w-50" data-testid="input-employee-id">
            <input
              className="w-100"
              type="text"
              name="employeeId"
              value={employeeForm.employeeId}
              onChange={(e) => updateInput(FORM_FIELDS.EMPLOYEE_ID, e, (v) => parseInt(v, 10))}
              placeholder="Employee ID"
            />
            {employeeForm.errors.employeeId ? <p className="error mt-2">Employee ID must be exactly 6 digits</p> : null}
          
          </div>
          <div className="layout-column align-items-start mb-10 w-50" data-testid="input-joining-date">
            <input
              className="w-100"
              type="date"
              name="joiningDate"
              value={employeeForm.joinedDate}
              onChange={(e) => updateInput(FORM_FIELDS.JOINED_DATE, e)}
              placeholder="Joining Date"
            />
            {
              employeeForm.errors.joinedDate ? <p className="error mt-2">Joining Date cannot be in the future</p> : null
            }
          </div>
          <button data-testid="submit-btn" type="button" onClick={() => dispatch({type: ACTIONS.RESET_FORM})} disabled={!isFormValid}>
            Submit
          </button>
        </div>
      );
    }
    
    export default EmployeeValidationForm;