Manage Forms the Easy Way on React with Final Form

Subscribe to my newsletter and never miss my upcoming articles

Video Tutorial

Overview

Managing and maintaining forms on a React application can be tricky sometimes when you have a lot of fields to keep track of on your application with different state variables and many handlers.

The Easy way to get rid of the form nightmare is to use a form management library like Final-Form or Formik.

The reason I picked up Final-Form for this tutorial instead of Formik is that it has a very simple API and you can easily integrate it to your existing application with Redux Store support.

Getting Started

Final Form is a Subscription-based, high performant form state management Framework can be used with VanillaJS Forms and React forms.

Subscription-based means you can keep track of the input fields you only choose.

So let’s try to build a simple form using Final-Form on a React Application we will be using CodeSandBox.io cause it provides a ready-made preconfigured development environments.

Start by creating a React Application based on the React Template.

If you would like to check out the final form we are going to build on this tutorial head to This Sandbox.

Component Structure

So let embed the Final Form core components into our components to be able to reuse our components throughout the whole application with different form cases.

So under your components folder create a Form and Input components.

import React from "react";
import { Form as FinalForm } from "react-final-form";

export function Form(props) {
  return (
    <FinalForm
      onSubmit={props.onSubmit}
      render={renderProps => (
        <form onSubmit={renderProps.handleSubmit}>
          {props.children(renderProps)}
        </form>
      )}
    />
  );
}

Final Form uses Render Props pattern to render the form fields with and provides form state through the props. So we simply export a Form Function Component with a render props pattern to provide the form state to fields.

You can look at FormState on the Final Form Docs.

In Final Form, each input is a field it could be from a simple text input to your custom input components.

import React from "react";
import styled from "styled-components";
import { useField } from "react-final-form";

const InputWrapper = styled.div`
  display: flex;
  flex-direction: column;
  align-items: flex-start;
`;

const InputContainer = styled.input`
  min-width: 4em;
  height: 2em;
  padding: 5px 15px;
  font-size: 19px;
  background-color: #ececec;
  outline: none;
  transition: all 250ms ease-in-out;
  color: #2f3337;
  border-radius: 9px;
  border: ${props => (props.error ? `1px solid #e74c3c` : 0)};
  box-shadow: "0px 0px 5px 1px #0783EF";

  &:hover {
    box-shadow: ${props =>
      props.error ? `0px 0px 5px 0px #e74c3c` : `0px 0px 5px 0px #0783EF`};
  }

  &:focus {
    outline: 0;
    box-shadow: ${props =>
      props.error ? `0px 0px 10px 1px #e74c3c` : `0px 0px 10px 1px #0783EF`};
  }

  &::placeholder {
    color: #6f6f6f;
  }
`;

const ErrorText = styled.div`
  margin-top: 5px;
  color: #e74c3c;
  font-size: 15px;
  padding: 0px 4px;
  min-height: 24px;
`;

export function Input(props) {
  const {
    input,
    meta: { error, touched, submitError }
  } = useField(props.name, {
    initialValue: props.initialValue,
    validate: props.validate
  });

  const inputProps = {
    ...props,
    error: touched && error && true,
    ...input
  };

  return (
    <InputWrapper>
      <InputContainer {...inputProps} />
      <ErrorText>{touched && (error || submitError) ? error : ""}</ErrorText>
    </InputWrapper>
  );
}

If you love CSS-in-JS and especially StyledComponents as I do, you can put your input CSS code in the same file as the Input component. So it is just for giving a better input style with some hover effects and showing red box-shadow on error using the help of props in styled-components.

If you prefer old CSS files I highly recommend moving to use CSS-IN-JS Frameworks.

The input in our case represents a Text Input so we use useField hook to get the current field info using its unique name among other fields in the same form.

We are also checking if the field is touched (the user clicked or typed something on the input) and if there any validation errors we will transition to error state of the InputContainer passing it error prop.

For showing Validation errors we are adding a new ErrorText component to display the error message right beneath the input (only if the field has a validation error).

Creating a Basic Form with Validation

Lets put our Form components together to create a basic Register form with custom validation.

First, import required modules.

import React from "react";
import "./styles.css";
import { Form } from "./components/form";
import { Input } from "./components/input";
import styled from "styled-components";
import validator from "validator";

const Group = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: flex-start;
  width: fit-content;
  margin-bottom: 1em;
`;

const FormWrapper = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
`;

const Label = styled.div`
  font-size: 20px;
  font-weight: 700;
  margin-bottom: 8px;
`;

We create a Group component which can group a Label with an input vertically with some margin in between.

The FormWrapper Wraps the forms of input groups and aligns them using a flexbox.

For the main App Component let’s create a basic form with two form groups one for username and the other for password and a Register button at the bottom.

export default function App() {
  return (
    <div className="App">
      <h1>React with Final Form</h1>
      <Form onSubmit={() => {}}>
        {props => (
          <FormWrapper>
            <Group>
              <Label>Username</Label>
              <Input
                name="username"
                placeholder="Username"
              />
            </Group>
            <Group>
              <Label>Password</Label>
              <Input name="passowrd" type="password" placeholder="Password" />
            </Group>
          </FormWrapper>
        )}
      </Form>
    </div>
  );
}

The Form component we created earlier user Render Props pattern so it takes a callback function passing FormState as props to it in order for the fields to access the current state.

I Already Explained The best React Design Patterns on This Video.

The Input Component renders a Field component so, we have to pass a unique name identifier to the input and other standard props as the placeholder.

The Final Form values object shape will use each filed unique name identifier as a key to the field value.

Finally, let’s add some validation to the username, we will need a package called validator which provides an out of the box functions that takes a value and returns a boolean whether or not the value satisfies the condition.

...
  <Input
    name="username"
    placeholder="Username"
    validate={v =>
      !validator.isEmail(v || "") && "Please Enter a Valid Email!"
    }
  />
...

The validate prop takes a callback that provides the current Field value and expects an undefined in return if no errors otherwise, you need to return the error message.

Validate.isEmail is imported from the Validator package.

Here is the Full Working Example:

No Comments Yet