Skip to content

Latest commit

 

History

History
316 lines (244 loc) · 7.43 KB

readme.md

File metadata and controls

316 lines (244 loc) · 7.43 KB

15 Login form Validation

Let's add validation support to this form.

For this we will use lc-form-validation library

Summary steps:

  • Install lc-form-validation library.
  • Refactor input component to a common component and include error validation info.
  • Let's define the validation for the form.
  • Let's hook it.

Prerequisites

Install Node.js and npm (v6.6.0) if they are not already installed on your computer.

Verify that you are running at least node v6.x.x and npm 3.x.x by running node -v and npm -v in a terminal/console window. Older versions may produce errors.

Steps to build it

  • Copy the content from 15 React Form and execute npm install.
npm install
  • Let's install the library (it includes already the typings).
npm install lc-form-validation --save-dev
  • To avoid having too much repeated code let's move to common an input component, including it's label plus validation text.

./common/forms/textFieldForm.tsx

import * as React from "react";
import TextField from "@material-ui/core/TextField";

interface Props {
  name: string;
  label: string;
  onChange: any;
  value: string;
  error?: string;
  type? : string;
}

const defaultProps : Partial<Props> = {
  type: 'text', 
}

const onTextFieldChange = (fieldId : string, onChange: (fieldId, value) => void) => (e) => {
  onChange(fieldId, e.target.value);
}

export const TextFieldForm : React.StatelessComponent<Props> = (props) => {
  const {name, label, onChange, value, error, type} = props;
  return (
    <>
        <TextField
          label={label}
          margin="normal"
          value={value}
          type={type}
          onChange={onTextFieldChange(name, onChange)}
        />
        <Typography 
          variant="caption" 
          color="error"
          gutterBottom>
          {props.error}
        </Typography>        
    </>
  )
}
  • Now let's define a basic validation for the form, we want to ensure both fields are informed.

./src/pages/login/loginValidations.ts

import {
  createFormValidation, ValidationConstraints, Validators,
} from 'lc-form-validation';

const loginFormValidationConstraints: ValidationConstraints = {
  fields: {
    login: [
      { validator: Validators.required },
    ],
    password: [
      { validator: Validators.required },
    ],    
  },
};

export const loginFormValidation = createFormValidation(loginFormValidationConstraints);
  • Let's create now a class to hold the dataFormErrors.

./src/login/viewmodel.ts

import { FieldValidationResult } from 'lc-form-validation';

export interface LoginFormErrors {
  login: FieldValidationResult;
  password: FieldValidationResult;
}

export const createDefaultLoginFormErrors = (): LoginFormErrors => ({
  login: new FieldValidationResult(),
  password: new FieldValidationResult(),
});
  • Now let's go for the component side.

  • First let's add the dataFormErrors to the state of the component.

./src/pages/login/loginPage.tsx

import { isValidLogin } from '../../api/login';
+ import {LoginFormErrors} from './viewmodel';

interface State {
  loginInfo: LoginEntity;
  showLoginFailedMsg: boolean;
+  loginFormErrors : LoginFormErrors;
}
  • Now let's update the onUpdate callback to include the validation.

./src/pages/login/loginPage.tsx

// Adding imports

import { isValidLogin } from '../../api/login';
+ import {LoginFormErrors, createDefaultLoginFormErrors} from './viewmodel';
+ import { loginFormValidation } from './loginValidations';

./src/pages/login/loginPageContainer.tsx

  constructor(props) {
    super(props);

    this.state = { loginInfo: createEmptyLogin(),
                   showLoginFailedMsg : false,
+                   loginFormErrors: createDefaultLoginFormErrors(),
    }
  }

+  // This could be simplified and made in one go
  updateLoginField = (name, value) => {
    this.setState({loginInfo: {
      ...this.state.loginInfo,
      [name]: value,
      }
    });

+    loginFormValidation.validateField(this.state.loginInfo, name, value)
+    .then((fieldValidationResult) => {

+        this.setState({loginFormErrors: {
+          ...this.state.loginFormErrors,
+          [name]: fieldValidationResult,
+        });
+   });
  }
  • We need to pass down dataFormErrors

./src/loginPageContainer.tsx

  public render() {
    return (
      <LoginPage
        onLogin={this.onLogin}
        onUpdateField={this.onUpdateLoginField}
        loginInfo={this.state.loginInfo}
+       loginFormErrors={this.state.loginFormErrors} 
      />
    )
  }
  • Now we need to define the property in the loginForm component.

./src/loginForm.tsx

import { LoginEntity } from "../../model/login";
+ import {LoginFormErrors} from './viewmodel';

interface Props {
  loginInfo: LoginEntity;
  updateField: (string, any) => void;
  doLogin: () => void;
+  loginFormErrors : LoginFormErrors;
}
  • Now let's update our components to match the new input component.

./src/common/pages/loginForm.tsx

import { LoginEntity } from "../../model/login";
import {LoginFormErrors} from './viewmodel';
+ import { TextFieldForm } from '../../common/forms/textFieldForm';

./src/common/pages/loginForm.tsx

export const LoginForm = (props: Props) => {
-   const { onLogin, onUpdateField, loginInfo } = props;
+   const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props;

-  const onTexFieldChange = (fieldId) => (e) => {
-    onUpdateField(fieldId, e.target.value);
-  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
-      <TextField
-        label="Name"
-        margin="normal"
-        value={loginInfo.login}
-        onChange={onTexFieldChange('login')}
-      />
+      <TextFieldForm
+        label="Name"
+        name="login"
+        value={loginInfo.login}
+        onChange={onUpdateField}
+        error={loginFormErrors.login.errorMessage}
+      />

-      <TextField
-        label="Password"
-        type="password"
-        margin="normal"
-        value={loginInfo.password}
-        onChange={onTexFieldChange('password')}
-      />
+      <TextFieldForm
+        label="Password"
+        name="password"
+        value={loginInfo.password}
+        onChange={onUpdateField}
+        error={loginFormErrors.password.errorMessage}
+      />


      <Button variant="contained" color="primary" onClick={onLogin}>
        Login
      </Button>
    </div>
  )
}
  • Let's give a try.
npm start
  • And let's add an alert (Excercise and a notification) when the user clicks and the form all the fields are valid.

./src/pages/login/loginPageContainer.tsx

  onLogin = () => {
+    loginFormValidation.validateForm(this.state.loginInfo)
+      .then((formValidationResult) => {
+          if(formValidationResult.succeeded) {
            if (isValidLogin(this.state.loginInfo)) {
              this.props.history.push('/pageB');
            } else {
              this.setState({ showLoginFailedMsg: true });
            }      
+          } else {
+            alert('error, review the fields');
+          const updatedLoginFormErrors = {
+             ...this.state.loginFormErrors,
+             ...formValidationResult.fieldErrors,
+          }

+          this.setState({loginFormErrors: updatedLoginFormErrors})

+          }
+      })
  }

// TODO: mapFormValidationResultToFieldValidationErrors

Excercise create a generic info snack bar and remove alert.