Skip to content

Latest commit

 

History

History
241 lines (194 loc) · 10.2 KB

formik-partII.md

File metadata and controls

241 lines (194 loc) · 10.2 KB

Easy forms with Formik - part II

Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris

This is the continuation of our first part on Formik, the amazing Forms library for React

This article is part of a series:

In this article we will cover:

  • Schema Validation with Yup, there is an alternate way to validate your input elements and that is by declaring a schema in Yup and simply assign that to an attribute on the Formik component
  • Async validation
  • Built-in components, make everything less verbose using some of Formiks built-in components

## Resources I have made a repo for both these articles, so if you get stuck have a look here Form demo repo

Built-in components

So far we have been using regular HTML elements like form and input to build our form and we have connected to events like onSubmit, onChange and onBlur. But we can actually be typing a lot less. Say hello to the following components:

  • Form, this replaces a normal form element
  • Field, this replaces any type of input element
  • ErrorMessage, this doesn't really replace any controls that you have but is a great component that given the attribute name is able to show your error message

Let's first look at a simple form and then rewrite it using the above-mentioned components:

import { Formik } from 'formik';
import React from 'react';

const FormikExample = () => (
  <Formik
    initialValues={{ name: '' }}
    validation={values => {
      let errors = {};
      if(!values.name) {
        errors.name = 'Name is required';
      }
      return errors;
    }}
    onSubmit={values ={
      console.log('submitted');
    }}
  >
  {({ handleSubmit, handleChange, values, errors }) => (
   <form onSubmit={handleSubmit}>
    <input name="name" onChange={handleChange} value={values.name} />
    {errors.name && 
    <span>{errors.name}</span>
    }
   </form>
  )
  }
  </Formik>
)

Ok, above we see what a minimal implementation looks like the classical way of doing it, that is using HTML elements like form and input.

Now lets clean this up using Formiks built-in controls:

import { Formik, Form, Field, ErrorMessage } from 'formik';
import React from 'react';


const FormikExample = () => (
  <Formik
    initialValues={{ name: '' }}
    validation={values => {
      let errors = {};
      if(!values.name) {
        errors.name = 'Name is required';
      }
      return errors;
    }}
    onSubmit={values ={
      console.log('submitted');
    }}
  >
  {({ handleSubmit, errors }) => (
   <Form onSubmit={handleSubmit}>
    <Field type="text" name="name" />
    <ErrorMessage name="name"/>
    }
   </Form>
  )
  }
  </Formik>
)

Not super impressed? Let's list what we don't need to type anymore:

  • the onChange disappears from each input element
  • the input element is replaced by Field component
  • the form element is replaced by Form component
  • the conditional {errors.name && disappears as well as ErrorMessage component takes care of that bit

Not enough? Well imagine you have 10 fields, that is at least 10 lines of code that disappears and it generally it just looks cleaner. Now to our next improvement, we can replace our validation() function with a schema, up next.

Schema validation with Yup

Ok, we've covered how we can really clean up our markup by using the builtin controls Form, Field and ErrorMessage. Next step is improving even more by replacing our validation property with a validationSchema property. For that to be possible we need to define a schema using the library Yup. So what does a schema look like :

import * as Yup from 'yup'

const schema = Yup.object().shape({
    firstName: Yup.string()
      .min(2, 'Too Short!')
      .max(50, 'Too Long!')
      .required('Required'),
    lastName: Yup.string()
      .min(2, 'Too Short!')
      .max(50, 'Too Long!')
      .required('Required'),
    email: Yup.string()
      .email('Invalid email')
      .required('Required'),
  });

The above schema defines three different fields firstName, lastName and email and gives them each attributes that they should adhere to:

  • firstName, this should be a string consisting of min 2 characters and maximum 50 characters and its also required
  • lastName, this is also a string with the same min/max requirements and it's also required
  • email, this is just a string that is required

As you can see the above is quite readable and by defining your data like this you save yourself from having to type a lot of if constructs checking if all attributes are fulfilled.

Let's now put it to use in our Formik element, like so:

<Formik validationSchema={schema}>

That's it, that is all you need to define your form data in a really expressive way, doesn't that give you a warm and fuzzy feeling? :)

Async validation

Ok, now to our last topic, asynchronous validation. So what's the scenario? Well, sometimes you have data that you can't really tell on client side only whether the entered value is correct or not. Imagine you have a form where you want to find out whether a company or certain web page domain is already taken? At that point, you most likely will need to make a call to an endpoint and the endpoint will not be coming back with the answer instantly.

Ok, we've set the scene, how do we solve this in Formik? Well, the validation property is able to accept a Promise as well. Really, you think? That easy? Well, the solution is in my mind a bit unorthodox, let me show you what I mean:

<Formik
  validate={values => {
    console.log('validating async');
    let errors = {};
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        errors.companyName = 'not cool';
        resolve('done');
      },3000);
      }).then(() => {
        if(Object.keys(errors).length) {
          throw errors;
        }
      });
    }}
>
// define the rest here
</Formik>

Looking at our validate implementation we see that we create a Promise that internally runs a setTimout to simulate it going to an endpoint that it takes time to get an answer from. At this point we set a errors.companyName to an error text:

setTimeout(() => {
  errors.companyName = 'not cool';
  resolve('done');
},3000);

In more real scenario we would probably call a function and depending on the functions answer we would possibly assign errors.companyName. I'll show you below what I mean:

isCompanyNameUnique(values.companyName).then(isUnique => {
  if(!isUnique) {
    errors.companyName = `companyName is not unique, please select another one`
  }
  resolve('done')
})

Next thing that happens in our code is that we invoke then(), that happens when we call resolve(). Something really interesting happens in there, we check the errors for any properties that might have been set and if so we throw an error with our errors object as an argument, like so:

.then(() => {
  if(Object.keys(errors).length) {
    throw errors;
  }
});

I don't know about you, but to me, this looks a bit weird. I would have thought providing validation with a Promise would have meant that a reject() of the Promise would have been a more intuitive way of doing it, like so:

// this to me would have been more intuitive, but observe, this is NOT how it works, so DONT copy this text but refer to the above code instead

validation={ values => 
  console.log('validating async');
  let errors = {};
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      errors.companyName = 'not cool';
      reject(errors);
    },3000);
  })
}}

Async on field level

So far we have shown how to do async validation on Forms level but if you think about would you really want that? Most likely you have a mix of fields where it's enough to validate some of them client side while only a minority if fields need async validation. In such a case it makes sense to apply validation per field. That is quite easy to achieve by typing like this:

<Field name="username" validate={this.validate} >

This is probably preferred if you got async validation on a field. As for the other fields, you can validate client side it's probably a good idea to define those in on the Formik components validationSchema and use Yup schemas for that as we've described above.

Words of caution

If we do have async validation in there make sure your validations don't run too often especially if the validation takes time. You don't want a 3-sec validation to trigger every time a key is typed, at most you want it when the user leaves the field to start typing in another field, we refer to this as the blur event. So make sure you set up your Formik component like this:

<Formik
  validateOnBlur={true} 
  validateOnChange={false} >

This does what you want, setting validateOnBlur to true is what you want, even though technically this is true by default. You want to be explicit with the next one though validateOnChange. You want this to be off, or set to false.

Summary

We've set out to cover built-in components like Form, Field and ErrorMessage, the end result was us cleaning up a lot of code.

Furthermore, we showed how we could get rid of our validation function by defining a schema using the Yup library.

Finally, we covered asynchronous validation and we discussed things to consider like when to validate and that it is probably best to have a field level validation for those few asynchronous fields that we have in a form and to use schema validation for the remaining fields.

That's it, that was the end of our article. I hope this part and the previous one have given you new hope that dealing with Forms in React doesn't have to that painful