Formik and Yup

Formik and Yup

When you use a form, it might have validations to see whether the input will be valid. There also might be error messages explaining what is wrong. We will look at formik and yup to help us achieve this.

Signup Form

Let's start by looking at a signup form.

First we will import formik and yup. Then, write the outline to use formik.

import React from "react";
import * as yup from "yup";
import { useFormik } from "formik";

const SignupForm = () => {
    const formSchema = yup.object().shape({})
    const formik = useFormik({
        initialValues: {},
        validationSchema: formSchema,
        onSubmit: 
    })
    return ()
}
export default SignupForm

Next, the values in our form will be username and password. We want these both to be strings, required and have a minimum and maximum. The initial values will just be empty.

const SignupForm = () => {
    const formSchema = yup.object().shape({
        username: yup.string().required("Username is required.").min(3).max(30),
        password: yup.string().required("Password is required.").min(3).max(30)
    })
    const formik = useFormik({
        initialValues: {
            username: "",
            password: ""
        },
        validationSchema: formSchema,
        onSubmit: 
    })
    return ()
}

The string written in required() will be displayed if there is nothing in the input.

The minimum and maximum will have there own type of error message.

Let's build the form next. Just looking at the return statement, we need a form with formik.

return (
        <form onSubmit={formik.handleSubmit}>
            <div>
                <label html="username">username</label>
                <input type='text' name='username' id='username' values={formik.values.username} onChange={formik.handleChange}/>
                <p className="errors">{formik.errors.username}</p>
            </div>
            <div>
                <label html="password">password</label>
                <input type='text' name='password' id='password' values={formik.values.password} onChange={formik.handleChange}/>
                <p className="errors">{formik.errors.password}</p>
            </div>
            <button type='Submit'>Submit</button>
        </form>
    )

The way we display the errors is using formik.errors.username, for example. The class name of errors is just adding the red color and smaller font to the error text.

Formik handles the onSubmit and onChange with the functions formik.onSubmit and formik.onChange. In the useFormik() above, onSubmit: was left blank. We would need to add a function here eventually.

Review Form

This form includes numbers.

const formSchema = yup.object().shape({
        session: yup.number().positive().integer().required("Session required").min(1).max(1000),
        level: yup.number().integer().positive().min(1).max(sessionAdvances.length-1).required("Level required")
    })

The requirements are a little different. Instead of string() we have number(). This needs to come first before talking about positive() and integer().

Deck Select

This page has a drop-down menu to select a deck. Since it is a drop-down menu, we need to import Select from "react-select".

import React from "react";
import Select from "react-select"
import * as yup from "yup"
import { useFormik } from "formik";

const DeckForm = ({filteredDeckOptions, card, onNewReview, setError, setIsView}) => {
    const formSchema=yup.object().shape({
        deck_id: yup.number().positive().integer().required("A deck is required.")
    })
    const formik = useFormik({
        initialValues: {
            deck_id: ''
        },
        validationSchema: formSchema,
        onSubmit: submitAddDeck
    })
    function submitAddDeck(values){
        fetch('/api/reviews', {
            method: 'POST', 
            headers: {
                "Accept": "application/json",
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                card_id: card.id,
                deck_id: values.deck_id
            })
        }).then(r=>{
            if(r.ok){
                r.json().then(data=>{
                    onNewReview(data)
                    setIsView(false)
                })
            } else {
                r.json().then(data=>setError(data.error))
            }
        })
    }
    const defaultValue = (options, value) => {
        return options ? options.find(option=>option.value===value):""
    }

    return (
        <form style={{display: 'flex'}} onSubmit={formik.handleSubmit}>
            <label>Add this card to another deck: </label>
            <Select 
                name='deck_options'
                id='deck_options'
                value={defaultValue(filteredDeckOptions, formik.values.deck_id)}
                options={filteredDeckOptions}
                onChange={value=>formik.setFieldValue('deck_id', value.value)}
            />
            <button type="Submit" >Submit</button>
            {formik.errors.deck_id ? <div className="errors">{formik.errors.deck_id}</div> : null}
        </form>
    )
}

export default DeckForm

There is a defaultValue function. Also, onChange needs to set the field value. The options used for the drop-down have to be in this form: {label: "", value: ""}.