Categories
React

Make Form Handling Easy in React Apps with Formik — Error Messages and Binding to Arrays and Nested Properties

Formik is a library that makes form handling in React apps easy.

In this article, we’ll look at how to handle form inputs with Formik.

Displaying Error Messages

We can display form validation error messages by rendering the values of the errors object.

For instance, we can write:

import React from "react";
import { Formik, Form, Field } from "formik";
import * as Yup from "yup";

const schema = Yup.object().shape({
  email: Yup.string().email("Invalid email").required("Required")
});

export const FormExample = () => (
  <div>
    <Formik
      initialValues={{
        email: ""
      }}
      validationSchema={schema}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      {({ errors, touched }) => (
        <Form>
          <Field name="email" />
          {touched.email && errors.email && <div>{errors.email}</div>}
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  </div>
);
export default function App() {
  return (
    <div className="App">
      <FormExample />
    </div>
  );
}

Then we render the errors.email property to get the value of the email field.

When we type in something that’s not an email address, we’ll see the error message displayed.

Arrays and Nested Objects

Formik works with arrays and nested objects.

For example, we can bind form fields to nested properties by writing:

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

export const FormExample = () => (
  <div>
    <Formik
      initialValues={{
        social: {
          facebook: "",
          twitter: ""
        }
      }}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      <Form>
        <Field name="social.facebook" />
        <Field name="social.twitter" />
        <button type="submit">Submit</button>
      </Form>
    </Formik>
  </div>
);
export default function App() {
  return (
    <div className="App">
      <FormExample />
    </div>
  );
}

In the initialValues prop, we have the social object with some properties inside.

To bind to those, we just set the name prop of the Field components to the property paths of those properties.

Then values in the onSubmit callback would have those values set.

We can also bind our Field input values to array entries.

To do this, we write:

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

export const FormExample = () => (
  <div>
    <Formik
      initialValues={{
        friends: ["james", "mary"]
      }}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      <Form>
        <Field name="friends[0]" />
        <Field name="friends[1]" />
        <button type="submit">Submit</button>
      </Form>
    </Formik>
  </div>
);
export default function App() {
  return (
    <div className="App">
      <FormExample />
    </div>
  );
}

We just set the name prop to the path to the array entry we want to bind to.

Also, we can remove nesting by adding properties with dots to separate the path segments.

For instance, we can write;

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

export const FormExample = () => (
  <div>
    <Formik
      initialValues={{
        "owner.name": ""
      }}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      <Form>
        <Field name="['owner.name']" />
        <button type="submit">Submit</button>
      </Form>
    </Formik>
  </div>
);
export default function App() {
  return (
    <div className="App">
      <FormExample />
    </div>
  );
}

We have the 'owner.name' property in initialValues .

Then we set the name prop to ['owner.name'] to do the binding automatically.

Conclusion

We can display form validation error messages and bind input fields to nested properties and array entries with Formik.

Categories
Vue

How to Create an Image Gallery App with Vue.js

Building an image gallery with Vue.js is easy. There are already libraries available to make a good looking image gallery app. With Vue.js, building an image gallery app is an enjoyable experience. We will use Vue Material to make the app look appealing and we use the Pixabay API located at https://pixabay.com/api/docs/

In our app, we will provide pages for image and video search, and the home page will have an infinite scrolling image gallery for letting users view the top image hits from the Pixabay API.

To get started, we install the Vue CLI by running npm i @vue/cli. Then we create the project by running vue create pixabay-app. When prompted for options, we choose the custom options and choose to include Babel, Vuex, Vue Router, and CSS preprocessor. We need all of them since we are building a single page with shared state between components, and the CSS preprocessor reduces the repetition of CSS.

Next, we have to install some libraries that we need for our app to function. Run npm i axios querystring vue-material vee-validate vue-infinite-scroll to install the packages we need. axios is our HTTP client, querystring is a package to massage objects into a query string, vue-material provides Material Design elements for our app to make it look appealing. vee-validate is a form validation library. vue-infinite-scroll is an infinite scroll library.

Now we can start writing code. We start by writing the shared code that our pages will use. In the components folder, we create a file called Results.vue and add the following:

<template>
  <div class="center">
    <div class="results">
      <md-card v-for="r in searchResults" :key="r.id">
        <md-card-media v-if="type == 'image'">
          <img :src="r.previewURL" class="image" />
        </md-card-media>
        <md-card-media v-if="type == 'video'">
          <video class="image">
            <source :src="r.videos.tiny.url" type="video/mp4" />
          </video>
        </md-card-media>
      </md-card>
    </div>
  </div>
</template>

<script>
export default {
  name: "results",
  props: {
    type: String
  },
  computed: {
    searchResults() {
      return this.$store.state.searchResults;
    }
  },
  data() {
    return {};
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.md-card {
  width: 30vw;
  margin: 4px;
  display: inline-block;
  vertical-align: top;
}
</style>

In this file, we get the data from the Vuex store and then display them to the user. this.$store is injected by Vuex and provides the state with the state property. The search results can be image or video, so we allow a type prop to let us distinguish the type of results. The state is in the computed property so that we can get it from the state.

Next, we add a mixin for getting making the requests. To do this, we add a mixins folder and add a file called photosMixin.js and fill it with the following:

const axios = require('axios');
const querystring = require('querystring');
const apiUrl = 'https://pixabay.com/api';
const apikey = 'Pixabay api key';

export const photosMixin = {
    methods: {
      getPhotos(page = 1) {
            const params = {
                page,
                key: apikey,
                per_page: 21
            }
            const queryString = querystring.stringify(params);
            return axios.get(`${apiUrl}/?${queryString}`);
        },

      searchPhoto(data) {
            let params = Object.assign({}, data);
            params['key'] = apikey;
            params['per_page'] = 21;
            Object.keys(params).forEach(key => {
                if (!params[key]) {
                    delete params[key];
                }
            })
            const queryString = querystring.stringify(params);
            return axios.get(`${apiUrl}/?${queryString}`);
        },

      searchVideo(data) {
            let params = Object.assign({}, data);
            params['key'] = apikey;
            params['per_page'] = 21;
            Object.keys(params).forEach(key => {
                if (!params[key]) {
                    delete params[key];
                }
            })
            const queryString = querystring.stringify(params);
            return axios.get(`${apiUrl}/videos/?${queryString}`);
        }
    }
}

This is where we get the images and video results from the Pixabay API. We massage the search data passed into a query string with the querystring package.

With the helper code complete, we can start building some pages. We first work on the home page, which will show a grid of image and the user can scroll down to see more. We create a file called Home.vue in the views folder which does not exist already and add the following:

<template>
  <div class="home">
    <div class="center">
      <h1>Home</h1>
      <div
        v-infinite-scroll="loadMore"
        infinite-scroll-disabled="busy"
        infinite-scroll-distance="10"
      >
        <md-card v-for="p in photos" :key="p.id">
          <md-card-media>
            <img :src="p.previewURL" class="image" />
          </md-card-media>
        </md-card>
      </div>
    </div>
  </div>
</template>

<script>
// @ is an alias to /src
import Results from "@/components/Results.vue";
import { photosMixin } from "@/mixins/photosMixin";

export default {
  name: "home",
  components: {
    Results
  },
  data() {
    return {
      photos: [],
      page: 1
    };
  },
  mixins: [photosMixin],
  beforeMount() {
    this.getAllPhotos();
  },
  methods: {
    async getAllPhotos() {
      const response = await this.getPhotos();
      this.photos = response.data.hits;
    },

    async loadMore() {
      this.page++;
      const response = await this.getPhotos(this.page);
      this.photos = this.photos.concat(response.data.hits);
    }
  }
};
</script>

<style lang="scss" scoped>
.md-card {
  width: 30vw;
  margin: 4px;
  display: inline-block;
  vertical-align: top;
}

.home {
  margin: 0 auto;
}
</style>

The v-infinite-scroll prop is the event handler provided by vue-infinite-scroll to let us do something when the user scrolls down. In this case, we call the loadMore function to load data from the next page and add it to the this.photos array. We display the images in a card view. infinite-scroll-distance=”10" means that we run the handler defined in v-infinite-scroll when the users scrolls through 90 percent of the current page.

Next we create an image search page. In the views folder, we create a file called ImageSearch.vue and add the following:

<template>
  <div class="imagesearch">
    <div class="center">
      <h1>Image Search</h1>
    </div>
    <form @submit="search" novalidate>
      <md-field :class="{ 'md-invalid': errors.has('q') }">
        <label for="q">Keyword</label>
        <md-input type="text" name="q" v-model="searchData.q" v-validate="'required'"></md-input>
        <span class="md-error" v-if="errors.has('q')">{{errors.first('q')}}</span>
      </md-field>

      <md-field :class="{ 'md-invalid': errors.has('minWidth') }">
        <label for="minWidth">Min Width</label>
        <md-input
          type="text"
          name="minWidth"
          v-model="searchData.min_width"
          v-validate="'numeric|min_value:0'"
        ></md-input>
        <span class="md-error" v-if="errors.has('minWidth')">{{errors.first('minWidth')}}</span>
      </md-field>

      <md-field :class="{ 'md-invalid': errors.has('minHeight') }">
        <label for="minHeight">Min Height</label>
        <md-input
          type="text"
          name="minHeight"
          v-model="searchData.min_height"
          v-validate="'numeric|min_value:0'"
        ></md-input>
        <span class="md-error" v-if="errors.has('minHeight')">{{errors.first('minHeight')}}</span>
      </md-field>

      <md-field>
        <label for="movie">Colors</label>
        <md-select v-model="searchData.colors" name="colors">
          <md-option :value="c" v-for="c in colorChoices" :key="c">{{c}}</md-option>
        </md-select>
      </md-field>

      <md-button class="md-raised" type="submit">Search</md-button>
    </form>
    <Results type='image' />
  </div>
</template>

<script>
// @ is an alias to /src
import Results from "@/components/Results.vue";
import { photosMixin } from "@/mixins/photosMixin";

export default {
  name: "home",
  components: {
    Results
  },
  data() {
    return {
      photos: [],
      searchData: {},
      colorChoices: [
        "grayscale",
        "transparent",
        "red",
        "orange",
        "yellow",
        "green",
        "turquoise",
        "blue",
        "lilac",
        "pink",
        "white",
        "gray",
        "black",
        "brown"
      ]
    };
  },
  mixins: [photosMixin],
  beforeMount() {
    this.$store.commit("setSearchResults", []);
  },
  computed: {
    isFormDirty() {
      return Object.keys(this.fields).some(key => this.fields[key].dirty);
    }
  },
  methods: {
    async search(evt) {
      evt.preventDefault();
      if (!this.isFormDirty || this.errors.items.length > 0) {
        return;
      }
      const response = await this.searchPhoto(this.searchData);
      this.photos = response.data.hits;
      this.$store.commit("setSearchResults", response.data.hits);
    }
  }
};
</script>

This page provides a form for the user to enter some search parameters like keyword, dimensions of the image, and colors. We check for correct data for each field with the v-validate prop provided by the vee-validate package. So for minWidth and minHeight we make sure that they are non-negative numbers. We display an error if it’s not.

We will also not allow submission if form data is invalid with the this.errors.items.length > 0 check which is also provided by vee-validate If everything is valid, we call the this.searchPhotos function provided by our photoMixin and set the result when returned in the store with the this.$store.commit function provided by the store. We pass image to the type prop of Results so that image results will be displayed.

Similarly, for the video search page, we create a new file called VideoSearch.vue in the views folder. We put the following:

<template>
  <div class="videosearch">
    <div class="center">
      <h1>Video Search</h1>
    </div>
    <form @submit="search" novalidate>
      <md-field :class="{ 'md-invalid': errors.has('q') }">
        <label for="q">Keyword</label>
        <md-input type="text" name="q" v-model="searchData.q" v-validate="'required'"></md-input>
        <span class="md-error" v-if="errors.has('q')">{{errors.first('q')}}</span>
      </md-field>

      <md-field :class="{ 'md-invalid': errors.has('minWidth') }">
        <label for="minWidth">Min Width</label>
        <md-input
          type="text"
          name="minWidth"
          v-model="searchData.min_width"
          v-validate="'numeric|min_value:0'"
        ></md-input>
        <span class="md-error" v-if="errors.has('minWidth')">{{errors.first('minWidth')}}</span>
      </md-field>

      <md-field :class="{ 'md-invalid': errors.has('minHeight') }">
        <label for="minHeight">Min Height</label>
        <md-input
          type="text"
          name="minHeight"
          v-model="searchData.min_height"
          v-validate="'numeric|min_value:0'"
        ></md-input>
        <span class="md-error" v-if="errors.has('minHeight')">{{errors.first('minHeight')}}</span>
      </md-field>

      <md-field>
        <label for="categories">Categories</label>
        <md-select v-model="searchData.category" name="categories">
          <md-option :value="c" v-for="c in categories" :key="c">{{c}}</md-option>
        </md-select>
      </md-field>

      <md-field>
        <label for="type">Type</label>
        <md-select v-model="searchData.video_type" name="type">
          <md-option :value="v" v-for="v in videoTypes" :key="v">{{v}}</md-option>
        </md-select>
      </md-field>

      <md-button class="md-raised" type="submit">Search</md-button>
    </form>
    <Results type="video" />
  </div>
</template>

<script>
import Results from "@/components/Results.vue";
import { photosMixin } from "@/mixins/photosMixin";

export default {
  name: "home",
  components: {
    Results
  },
  data() {
    return {
      photos: [],
      searchData: {},
      videoTypes: ["all", "film", "animation"],
      categories: `
       fashion, nature, backgrounds,
       science, education, people, feelings,
       religion, health, places, animals, industry,
       food, computer, sports, transportation,
       travel, buildings, business, music
      `
        .replace(/ /g, "")
        .split(",")
    };
  },
  mixins: [photosMixin],
  beforeMount() {
    this.$store.commit("setSearchResults", []);
  },
  computed: {
    isFormDirty() {
      return Object.keys(this.fields).some(key => this.fields[key].dirty);
    }
  },
  methods: {
    async search(evt) {
      evt.preventDefault();
      if (!this.isFormDirty || this.errors.items.length > 0) {
        return;
      }
      const response = await this.searchVideo(this.searchData);
      this.photos = response.data.hits;
      this.$store.commit("setSearchResults", response.data.hits);
    }
  }
};
</script>

We let users search by keywords, type, dimensions, and category. The logic for form validation and search are similar to the image search page, except that we pass video to the type prop of Results so that image results will be displayed.

In App.vue, we add the top bar and a left side menu for navigation. We replace the existing code with the following:

<template>
  <div id="app">
    <md-toolbar>
      <md-button class="md-icon-button" @click="showNavigation = true">
        <md-icon>menu</md-icon>
      </md-button>
      <h3 class="md-title">Pixabay App</h3>
    </md-toolbar>
    <md-drawer :md-active.sync="showNavigation" md-swipeable>
      <md-toolbar class="md-transparent" md-elevation="0">
        <span class="md-title">Pixabay App</span>
      </md-toolbar>

      <md-list>
        <md-list-item>
          <router-link to="/">
            <span class="md-list-item-text">Home</span>
          </router-link>
        </md-list-item>

        <md-list-item>
          <router-link to="/imagesearch">
            <span class="md-list-item-text">Image Search</span>
          </router-link>
        </md-list-item>

        <md-list-item>
          <router-link to="/videosearch">
            <span class="md-list-item-text">Video Search</span>
          </router-link>
        </md-list-item>
      </md-list>
    </md-drawer>

<router-view />
  </div>
</template>

<script>
export default {
  name: "app",
  data: () => {
    return {
      showNavigation: false
    };
  }
};
</script>

<style>
.center {
  text-align: center;
}

form {
  width: 95vw;
  margin: 0 auto;
}

.md-toolbar.md-theme-default {
  background: #009688 !important;
  height: 60px;
}

.md-title,
.md-toolbar.md-theme-default .md-icon {
  color: #fff !important;
}
</style>

The md-list contains the links to our pages, and we have a showNavigation flag to store the menu state and let us toggle the menu.

In main.js, we include the libraries that we used in this app:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import VueMaterial from 'vue-material';
import VeeValidate from 'vee-validate';
import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'
import infiniteScroll from 'vue-infinite-scroll'

Vue.use(infiniteScroll)
Vue.use(VueMaterial);
Vue.use(VeeValidate);
Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

In router.js, we add our routes so that users can see the app when they type in the URL or click on links on the menu:

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue';
import Search from './views/Search.vue';

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/search',
      name: 'search',
      component: Search
    }
  ]
})

In store.js, we put:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    searchResults: []
  },
  mutations: {
    setSearchResults(state, payload) {
      state.searchResults = payload;
    }
  },
  actions: {

  }
})

This allows the results to be set from the pages and displayed in the Results component. The this.$store.commit function sets the data and the this.$store.state property retrieves the state.

Categories
React

Make Form Handling Easy in React Apps with Formik— Field Level Validation

Formik is a library that makes form handling in React apps easy.

In this article, we’ll look at how to handle form inputs with Formik.

Field Level Validation

We can add field-level validation by creating validation functions and passing it into the validate prop of the Field component.

For instance, we can write:

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

function validateEmail(value) {
  let error;
  if (!value) {
    error = "Required";
  } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i.test(value)) {
    error = "Invalid email address";
  }
  return error;
}

export const FormExample = () => (
  <div>
    <h1>Signup</h1>
    <Formik
      initialValues={{
        email: ""
      }}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      {({ errors, touched, isValidating }) => (
        <Form>
          <Field name="email" validate={validateEmail} />
          {errors.email && touched.email && <div>{errors.email}</div>}
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  </div>
);

export default function App() {
  return (
    <div className="App">
      <FormExample />
    </div>
  );
}

We create the validateEmail function that takes the value parameter, which has the input value.

Then we check the value and return validation error messages after we checked if the value is valid.

In FormExample , we create the form with the Formik and Form components.

initialValues has the initial value of each field.

The Field component has the name prop set to the same value as in the initialValues .

validate has the validation function we created earlier.

errors has the error message.

touched has the touched state of the input.

isValidating tells us whether the form is being validated.

Manually Triggering Validation

We can manually trigger validation with Formik.

For instance, we can write:

import React from "react";
import { Formik, Form, Field } from "formik";
function validateUsername(value) {
  let error;
  if (value === "admin") {
    error = "Nice try!";
  }
  return error;
}

export const FormExample = () => (
  <div>
    <h1>Signup</h1>
    <Formik
      initialValues={{
        username: "",
      }}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      {({ errors, touched, validateField, validateForm }) => (
        <Form>
          <Field name="username" validate={validateUsername} />
          {errors.username && touched.username && <div>{errors.username}</div>}
          <button type="button" onClick={() => validateField("username")}>
            Check Username
          </button>
          <button
            type="button"
            onClick={async () => {
              const result = await validateForm();
              console.log(result);
            }}
          >
            Validate All
          </button>
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  </div>
);

export default function App() {
  return (
    <div className="App">
      <FormExample />
    </div>
  );
}

We have the validateUsername function that validates the username field.

In the Form element, we have the Check Username button that calls validateField with 'username' , which is the name value of the Field component.

The names will be matched so validateUsername function will be run.

We can validate all the fields in the form with the validateForm function, which returns a promise with any errors that are found.

So if we type in ‘admin’ into the input, the returned promise will resolve to:

{username: "Nice try!"}

Conclusion

We can add field level validation and manually trigger form validation with Formik.

Categories
React

Make Form Handling Easy in React Apps with Formik and Yup — withFormik and Yup

Formik is a library that makes form handling in React apps easy.

Yup is a library that integrates with Formik.

In this article, we’ll look at how to handle form inputs with Formik.

Validation

One way to add validation with Formik is to transform our form component into a form with validation with the withFormik higher-order component.

For instance, we can write:

import React from "react";
import { withFormik } from "formik";

const MyForm = (props) => {
  const {
    values,
    touched,
    errors,
    handleChange,
    handleBlur,
    handleSubmit
  } = props;
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        onChange={handleChange}
        onBlur={handleBlur}
        value={values.email}
        name="email"
      />
      {errors.email && touched.email && <div id="feedback">{errors.email}</div>}
      <button type="submit">Submit</button>
    </form>
  );
};

const MyEnhancedForm = withFormik({
  mapPropsToValues: () => ({ email: "" }),
  validate: (values) => {
    const errors = {};
    if (!values.email) {
      errors.email = "Required";
    } else if (
      !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i.test(values.email)
    ) {
      errors.email = "Invalid email address";
    }

    return errors;
  },

  handleSubmit: (values, { setSubmitting }) => {
    setTimeout(() => {
      alert(JSON.stringify(values, null, 2));
      setSubmitting(false);
    }, 1000);
  },

  displayName: "BasicForm"
})(MyForm);

export default function App() {
  return (
    <div className="App">
      <MyEnhancedForm />
    </div>
  );
}

to do that.

We create the MyForm component to get the properties we need from the props .

values has the inputted values.

touched has the touched state of each input.

errors has the form validation errors we set in the validate method/

handleChange has the form change handler.

handleBlur has the blur handler.

handleSubmit has the submit handler.

Next, we create the MyEnhancedForm compoennt which has the validation.

mapPropsToValues is set to a function that returns the initial values.

validate returns an error object after checking the values from the values parameter.

handleSubmit has the form submit function.

values has the form values.

setSubmitting has a function to set the submit state of the form.

displayName sets the displayName property of the component for debugging.

Then we use MyEnhancedForm in App to render the form.

Basic Validation with Yup

To make form validation easier, we can use the Yup library to add validation to our form.

For instance, we can write:

import React from "react";
import { Formik, Form, Field } from "formik";
import * as Yup from "yup";

const SignupSchema = Yup.object().shape({
  email: Yup.string().email("Invalid email").required("Required")
});

export const ExampleForm = () => (
  <div>
    <h1>Signup</h1>
    <Formik
      initialValues={{
        email: ""
      }}
      validationSchema={SignupSchema}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      {({ errors, touched }) => (
        <Form>
          <Field name="email" type="email" />
          {errors.email && touched.email ? <div>{errors.email}</div> : null}
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  </div>
);

export default function App() {
  return (
    <div className="App">
      <ExampleForm />
    </div>
  );
}

We create the SignupSchema with the Yup.object() method.

We pass in an object into the shape method to add validation to the object.

To validate strings, we call Yup.string().email() method.

The argument of 'email' has the validation error message.

required makes the field required.

Then we just pass the returned object into the validationSchema prop.

onSubmit has the submit handler.

initialValues has the initial values.

Then to create the Field object, we set the name to the property, we have in thew shape method’s argument to apply the form validation.

touched has the touched state of the input.

Conclusion

We can add form validation with Formik, and we can make this easier with Yup.

Categories
React

Make Form Handling Easy in React Apps with Formik

Formik is a library that makes form handling in React apps easy.

In this article, we’ll look at how to handle form inputs with Formik.

Installation

We can install Formik by running:

npm install formik --save

or

yarn add formik

Basic Usage

We can use it by writing:

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

export default function App() {
  return (
    <div className="App">
      <Formik
        initialValues={{ email: "", password: "" }}
        validate={(values) => {
          const errors = {};
          if (!values.email) {
            errors.email = "Required";
          } else if (
            !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i.test(values.email)
          ) {
            errors.email = "Invalid email address";
          }
          return errors;
        }}
        onSubmit={(values, { setSubmitting }) => {
          setTimeout(() => {
            alert(JSON.stringify(values, null, 2));
            setSubmitting(false);
          }, 400);
        }}
      >
        {({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          handleSubmit,
          isSubmitting
        }) => (
          <form onSubmit={handleSubmit}>
            <input
              type="email"
              name="email"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.email}
            />
            {errors.email && touched.email && errors.email}
            <input
              type="password"
              name="password"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.password}
            />
            {errors.password && touched.password && errors.password}
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </form>
        )}
      </Formik>
    </div>
  );
}

initialValues has the initial values.

validate is a function that lets us validate form values entered.

values has the values that we entered.

We set the errors object with the errors for the fields.

onSubmit has a function that has the entered values in the values object.

setSubmitting is a function that can we can run to set the form submit state.

In the form element, we set the onSubmit form to the handleSubmit function from the parameter of the render prop to run onSubmit if it’s value.

values.email and values.password have the form value for each field.

handleChange lets us incorporate the entered values into the values object in various places.

onBlur has the handleBlur method to handle blur events.

touched has the touched state for each form field.

isSubmitting has the submitting state of the form.

We can simplify the code with the Form , Field , and ErrorMessage components.

For instance, we can reduce the code above to:

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

export default function App() {
  return (
    <div className="App">
      <Formik
        initialValues={{ email: "", password: "" }}
        validate={(values) => {
          const errors = {};
          if (!values.email) {
            errors.email = "Required";
          } else if (
            !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i.test(values.email)
          ) {
            errors.email = "Invalid email address";
          }
          return errors;
        }}
        onSubmit={(values, { setSubmitting }) => {
          setTimeout(() => {
            alert(JSON.stringify(values, null, 2));
            setSubmitting(false);
          }, 400);
        }}
      >
        {({ isSubmitting }) => (
          <Form>
            <Field type="email" name="email" />
            <ErrorMessage name="email" component="div" />
            <Field type="password" name="password" />
            <ErrorMessage name="password" component="div" />
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </Form>
        )}
      </Formik>
    </div>
  );
}

The components replace the form input elements and error text.

As long as the name values match between the Field and ErrorMessage , we’ll see the same values displayed.

Conclusion

We can handle forms in React easily with Formik.