React Forms

From bibbleWiki
Jump to navigation Jump to search

Introduction

  • Controlled Forms
  • Uncontrolled Forms
  • Using Formik Library
  • Validation
  • Creating reusable custom form elements
  • Uncontrolled forms using React
  • React Hook Form to create uncontrolled forms

Controlled forms

In react we can pass state management to the react component. This is what a controlled form is. It's advantages are

  • Instant Feedback
  • Disable controls dynamically
  • Formats the input data e.g. dates 25-03-2001

Example using UseState

  const [password, setPassword] = useState("");
...
      <Form onSubmit={handleSubmit} className="row g-3 needs-validation">
        <div>
          <div className="col-md-4">
            <Form.Group size="lg" controlId="password">
              <Form.Label>Password</Form.Label>
              <Form.Control
                type="password"
                value={password}
                onChange={(e) => onPasswordChange(e)}
              />
              <div className="invalid-feedback">{passwordError}</div>
              <div className="valid-feedback">Password looks good!</div>
            </Form.Group>
          </div>
        </div>
        <div>
          <div className="col-12">
            <Button
              type="submit"
              className="btn btn-primary"
              disabled={isSubmitting || !formValid}
            >{`${isSubmitting ? "Logging In" : "Login"}`}</Button>
          </div>
        </div>
      </Form>
...

Using React Components

import React from "react";

class EmailForm extends React.Component {
    constructor(props) {
        super(props);
    }
    this.state = {value: ''};
    this.handleChange = this.handleChange.bind(this);
    
    handleChange(event) {
        this.setState({value: event.target.value});
    }
    
    render() {
        return (
            <form>
                <input type="email" value={this.state.value} onChange={this.handleChange} />
            </form>
        );
    }
}

Uncontrolled Forms

Uncontrolled forms are when the DOM maintains the states and a reference is stored to it in react.

Using Formik Library

Advantages

This is what is suggests.

  • Reduces Verbosity
  • Reduces code for state and callbacks
  • Reduces errors
  • Tracks values, errors and visited fields
  • Hooks up appropriate callback functions
  • Helpers for sync and async validation and showing errors
  • Sensible defaults

Components of Formik

Here are the components which make up a Formik form. The first being the component responsible for controlling the form

  • Formik
  • Form
  • Field
  • ErrorMessage

Internally Formik maintains three maps.

Formik Using Components

This is the example with components. This now been re-implemented using hooks. It does use styled components around the controls which I personally found to be a bit hard to debug.

import React from "react";
import styled from "styled-components";
import { Formik, Field, Form, ErrorMessage } from "formik";

const SigninForm = styled(Form)`
  display: flex;
  flex-direction: column;
  padding: 30px;
  border: 1px solid black;
`;

const Container = styled.div`
  display: flex;
  flex-direction: column;
  flex: 1;
  height: 100%;
  align-items: center;
`;

const ContentContainer = styled.div`
  display: flex;
  flex-direction: column;
  width: 600px;
  margin-top: 50px;
`;

const Title = styled.h1`
  white-space: pre-line;
`;

const Label = styled.label`
  margin-top: 20px;
  font-size: 24px;
`;

const EmailField = styled(Field)`
  height: 40px;
  font-size: 24px;
`;

const ErrorLabel = styled.div`
  color: red;
  font-size: 26px;
`;

const PasswordField = styled(Field)`
  height: 40px;
  font-size: 24px;
`;

const CheckboxContainer = styled.div`
  display: flex;
  height: 50px;
  align-items: center;
`;

const RememberMeCheckboxField = styled(Field)`
  margin-top: 10px;
`;

const CheckboxLabel = styled(Label)`
  margin-top: 7px;
  margin-left: 10px;
`;

class LoginFormik extends React.Component {
  constructor(props) {
    super(props);

    this.handleSubmit = LoginFormik.handleSubmit.bind(this);
    this.handleValidation = LoginFormik.handleValidation.bind(this);
  }

  static handleSubmit(values) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
        // eslint-disable-next-line no-alert
        alert(JSON.stringify(values));
      }, 5000);
    });
  }

  static handleValidation(values) {
    const errors = {};

    if (!values.email) {
      errors.email = "Email cannot be empty";
    }

    if (!values.password) {
      errors.password = "Password cannot be empty";
    } else if (values.password.length < 8) {
      errors.password = "Password must be at least 8 characters";
    }
    return errors;
  }

  render() {
    return (
      <Container>
        <ContentContainer>
          <Title>Signin Form</Title>

          <Formik
            initialValues={{ email: "", password: "", rememberMe: false }}
            onSumbit={this.handleSumbit}
            validate={this.handleValidation}
          >
            {() => (
              <SigninForm>
                <Label>Email</Label>
                <EmailField name="email" type="email" />
                <ErrorMessage name="email">
                  {(error) => <ErrorLabel>{error}</ErrorLabel>}
                </ErrorMessage>

                <Label>Password</Label>
                <PasswordField name="password" type="password" />
                <ErrorMessage name="password">
                  {(error) => <ErrorLabel>{error}</ErrorLabel>}
                </ErrorMessage>

                <CheckboxContainer>
                  <RememberMeCheckboxField type="checkbox" name="rememberMe" />
                  <CheckboxLabel>Remember Me</CheckboxLabel>
                </CheckboxContainer>
              </SigninForm>
            )}
          </Formik>
        </ContentContainer>
      </Container>
    );
  }
}
export default LoginFormik;

Validation

Formik provides two types of validation and they are not a surprise

  • Field level
  • Form level

Field Level

Note the returning of undefined not null

...
class LoginFormik extends React.Component {
  static validatePassword(value) {
    if (!value) {
      return "Password cannot be empty";
    }
    if (value.length < 5) {
      return "Very Weak";
    }
    if (value.length < 8) {
      return "Weak";
    }
    return undefined;
  }
...
                <Label>Password</Label>
                <PasswordField
                  name="password"
                  type="password"
                  validate={LoginFormik.validatePassword}
                />