Formik adoption guide: Overview, examples, and alternatives

leemeganj

Megan Lee

Posted on September 12, 2024

Formik adoption guide: Overview, examples, and alternatives

Written by Alexander Godwin✏️

React forms can be lengthy and complicated. Likewise, most tools that assist with building forms in React often perform an excessive amount of “magic” underneath the hood, which tends to hinder performance to some extent. Formik is a small JavaScript library built in response to the hardships experienced when creating large forms in React.

In this adoption guide, we’ll discuss Formik’s features and use cases that make it a great choice for React projects with forms. We’ll also take a look at how to get started with Formik, including styling and animating it, and when an alternate form library might be better suited to your needs.

By the end of this guide, you should have a strong sense of when and how to adopt Formik effectively. Let’s get right into it.

What is Formik?

Jared Palmer created Formik out of his frustration from building React forms while building an extensive internal administrative dashboard alongside Eon White. Faced with about 30 distinct forms, he quickly realized there was a significant advantage in standardizing input components and streamlining the data flow through the forms.

When creating Formik, Palmer’s goal was to develop a scalable, performant, form helper with a minimal API that does the “annoying stuff” and leaves the rest up to the developer.

Formik helps developers with:

  • Getting values in and out of form state
  • Validation and error messages
  • Handling form submission

Formik tracks the state of your form and then uses props to expose it to your form along with a few reusable methods and event handlers, including handleChange, handleBlur, and handleSubmit. As expected, handleChange and handleBlur determine which field has to be updated based on an id or name attribute.

xQuoting directly from the author, “Formik initially started by expanding on this little higher-order component by Brent Jackson, some naming conventions from Redux-Form, and (most recently) the render props approach popularized by React-Motion and React-Router 4.”

Further reading:

Why choose Formik?

Formik has become widely accepted in the industry as one of the best form-building tools. In this section we’ll see why that’s the case and hopefully make a strong argument for why you should try out Formik in your next project.

Within the React ecosystem, Formik is a well-liked module that makes form development and maintenance easier. It simplifies creating and managing complicated forms in React apps by offering a set of tools and patterns to assist with form state, validation, and submission.

Some of Formik’s advantages include:

  • Ease of use — Getting started with Formik is easy, requiring only a few lines of code. It’s incrementally adoptable and makes form development in React much easier, handling form values, validation, and form submission seamlessly. We'll see in the next section how Formik provides an object that holds errors, the form state, and much more
  • Bundle size — Formik is only about 12.7 kB minified and gzipped. With such a small bundle size, you should have no problem introducing Formik to your application without leading to any noticeable bloat
  • Community and ecosystem — There’s a very active community in the Formik ecosystem, since it’s currently widely accepted as one of the best form-building tools for React. The thriving community has conveniently provided some open source integrations that would have otherwise been a pain to build on your own
  • Learning curve — Learning Formik shouldn’t be hard if you have some prior experience with building React forms. At its most basic, Formik augments your form and provides additional data for tracking state, errors, and touched values
  • Documentation — Formik’s documentation is well-written and provides a variety of resources such as tutorials, guides, API references, and examples
  • Integrations — There is currently a set of open source UI framework wrappers that are readily available for use with Formik. Formik also supports validation libraries like Yup

Despite all this, it would be naive to think these advantages automatically make Formik the best choice in any situation. You should also consider when not to use Formik for reasons like the following:

  • Other frameworks — Formik is designed specifically for React. If you’re working in a non-React environment, such as Angular, Vue, or a plain JavaScript project, Formik is not applicable
  • Performance concerns — Formik can cause performance overhead, particularly when creating large forms with lots of fields and intricate validations. You may need a more straightforward solution or specialized form management suited to your requirements if you observe performance problems, such as sluggish input fields or form submissions
  • Simple forms — If you have a very basic form with few fields and little validation, it might be unnecessary to use Formik. Under such circumstances, using React's useState and useEffect Hooks to manage form state and validation manually may be simpler and require less code
  • Learning curve — As stated previously, Formik is easy to pick up if you have prior experience with React. However, for teams who are new to React or would rather not add other libraries with their learning curves, using basic React form handling can be a better option. The addition of Formik introduces an additional layer of abstraction, requiring knowledge of its patterns and API

Let’s take a quick look at how to get started with Formik next.

Further reading:

Getting started with Formik

We’ll use create-react-app for this simple example:

npx create-react-app my-app
Enter fullscreen mode Exit fullscreen mode

To start using Formik, run this command:

npm i formik
//or
yarn add formik
Enter fullscreen mode Exit fullscreen mode

We’ll build a simple form with two fields — username and password — and a Submit button that alerts the content of our form.

Delete the initial files in the my-app folder created by create-react-app and then replace it with the following files. First, this index.js file:

##index.js

import React from "react"
import ReactDOM from "react-dom/client"
import "./index.css"

const SimpleForm = () => {
  return (
    <div className="container">
      <form id="simple-form">
        <div className="form-group">
          <label htmlFor="email">email:</label>
          <input
            type="text"
            id="email"
            name="email"
          />
        </div>
        <div className="form-group">
          <label htmlFor="password">Password:</label>
          <input
            type="password"
            id="password"
            name="password"
          />
        </div>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}
function App() {
  return <SimpleForm />
}
const rootElement = ReactDOM.createRoot(document.getElementById("root"))
rootElement.render(<App />)

export default SimpleForm
Enter fullscreen mode Exit fullscreen mode

Second, this index.css file:

/* index.css */

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}
/* SimpleForm.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font-family: Arial, sans-serif;
  background-color: #f0f0f0;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}
.container {
  background-color: #ffffff;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
form {
  display: flex;
  flex-direction: column;
}
.form-group {
  margin-bottom: 1rem;
}
label {
  font-weight: bold;
  margin-bottom: 0.5rem;
}
input[type="text"],
input[type="password"] {
  padding: 0.5rem;
  font-size: 1rem;
  border: 1px solid #ccc;
  border-radius: 4px;
}
button {
  padding: 0.75rem 1.5rem;
  font-size: 1rem;
  background-color: #007bff;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}
button:hover {
  background-color: #0056b3;
}
Enter fullscreen mode Exit fullscreen mode

The boilerplate code above is for a basic React form. We’ll apply gradual changes to the form using Formik to clearly understand how Formik works.

Import the useFormik Hook from the formik package and use it to set the initial values of the fields in our form:

 class="language-javascript hljs"import { useFormik } from "formik"

//at the beginning of the simpleForm function
const formik = useFormik({
    initialValues: {
      email: "",
      password: "",
    },
    onSubmit: (values) => {
      alert(JSON.stringify(values, null, 2))
    },
  })
Enter fullscreen mode Exit fullscreen mode

Now we can use the object returned from the useFormik Hook in our form as follows:

<form id="simple-form" onSubmit={formik.handleSubmit}>
    <div className="form-group">
      <label htmlFor="email">email:</label>
      <input
        type="text"
        id="email"
        name="email"
        onChange={formik.handleChange}
        value={formik.values.email}
      />
    </div>
    <div className="form-group">
      <label htmlFor="password">Password:</label>
      <input
        type="password"
        id="password"
        name="password"
        onChange={formik.handleChange}
        value={formik.values.password}
      />
    </div>
    <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Formik provides us with the formik.handleChange and formik.handleSubmit helper methods, which function as you’d expect. The formik.values field contains all the values in our form as set in the useFormik Hook.

With the above code, we created a simple form that has two fields and a Submit button.

Formik features

In this section, we’ll continue building on our form while exploring more Formik features.

Values

The first Formik feature we’ll look at is the formik.values field we saw briefly in the previous section.

The formik.values field helps track the state of our application. We can set the initial values of the fields in our application by using the useFormik Hook and passing an initialValues object.

Validation

The form we built above lacks any means for validating user input. Let’s see how we can add validation to our form.

Formik provides a concise method to handle user input validation by using the validate function of the useFormik Hook. The values of each of our fields are passed to the validate function, with which we can validate inputs.

Add the following code to the top of the simpleForm function:

 const validate = (values) => {
    const errors = {}
    if (!values.email) {
      errors.email = "Email is Required"
    } else if (
      !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
    ) {
      errors.email = "Invalid email address"
    }
    if (!values.password) {
      errors.password = "Password is Required"
    } else if (values.password.length < 6)
      errors.password = "Password must contain 6 or more characters"
    return errors
 }
Enter fullscreen mode Exit fullscreen mode

Then add the validate function to the useFormik Hook:

const formik = useFormik({
    //...
    validate,
    //..
})
Enter fullscreen mode Exit fullscreen mode

Great! With that, we’ve added validation for user inputs in the username and password fields. If the username field is blank or contains an invalid email address, Formik will return an errors object. This will also happen if the password field is blank or the password isn’t long enough.

Further reading:

Errors

The validate function we wrote above returns an errors object. The errors object, which is available to us via formik.errors, contains the error messages we set for each field in the validate function.

We can now update our form fields to display the errors as follows:

<div className="container">
      <form id="simple-form" onSubmit={formik.handleSubmit}>
        <div className="form-group">
          <!-- code left as it was -->
          {formik.errors.email ? (
            <div> {formik.errors.email} </div>
          ) : null}
        </div>
        <div className="form-group">
         <-- code left as it was -->
          {formik.errors.password ? (
            <div> {formik.errors.password} </div>
          ) : null}
        </div>
        <button type="submit">Submit</button>
      </form>
  </div>
Enter fullscreen mode Exit fullscreen mode

Touched

Formik provides a way to track fields that the user has visited previously. formik.touched contains all the fields in our form with a boolean value set to true if the user has visited the field. We use formik.handleBlur handler to update the touched fields.

We can update our form to track the visited fields and display errors only if the user has visited a field:

<div className="container">
    <form id="simple-form" onSubmit={formik.handleSubmit}>
      <div className="form-group">
        <label htmlFor="email">email:</label>
        <input
          type="text"
          id="email"
          name="email"
          onChange={formik.handleChange}
          onBlur={formik.handleBlur} //track touch field
          value={formik.values.email}
        />
        <!-- update to show errors only if field has been visited -->
        {formik.touched.email && formik.errors.email ? (
          <div> {formik.errors.email} </div>
        ) : null}
      </div>
      <div className="form-group">
        <label htmlFor="password">Password:</label>
        <input
          type="password"
          id="password"
          name="password"
          onChange={formik.handleChange}
          onBlur={formik.handleBlur} //track touched field
          value={formik.values.password}
        />
        <!-- update to show errors only if field has been visited -->
        {formik.touched.password && formik.errors.password ? (
          <div> {formik.errors.password} </div>
        ) : null}
      </div>
      <button type="submit">Submit</button>
    </form>
  </div>
Enter fullscreen mode Exit fullscreen mode

Use cases for Formik

Now that we understand how Formik works, we can take a look at some use cases:

  • Basic form handling — Formik is generally used to manage form states (values, touched, errors) with ease. It typically handles form submissions and resetting form states, providing a clean and intuitive API for form creation. However, keep in mind that Formik may be unnecessary for very simple forms, as mentioned above
  • Form validation — Formik provides integration with libraries like Yup to handle form validation. As seen in previous sections, it provides built-in validation methods and can handle both synchronous and asynchronous validations
  • Dynamic forms — Formik is great for managing forms with dynamic fields that change based on user input or other conditions. It offers supporting arrays of fields (e.g., lists of items with repeatable fields)
  • Complex form workflows — Formik can be used for building and handling multi-step forms with ease. You can use it to manage complex form logic, such as conditional fields and nested fields

Further reading:

Styling and animating Formik forms

In this section, we’ll focus on different ways we can style our Formik forms, including traditional CSS, CSS-in-JS libraries, and CSS frameworks. Here’s an example using CSS modules:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import styles from './LoginForm.module.css';

const LoginForm = () => {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      validationSchema={Yup.object({
        email: Yup.string().email('Invalid email address').required('Required'),
        password: Yup.string().required('Required'),
      })}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          console.log(values);
          setSubmitting(false);
        }, 400);
      }}
    >
      <Form className={styles.form}>
        <div className={styles.fieldContainer}>
          <label htmlFor="email">Email</label>
          <Field name="email" type="email" className={styles.field} />
          <ErrorMessage name="email" component="div" className={styles.error} />
        </div>

        <div className={styles.fieldContainer}>
          <label htmlFor="password">Password</label>
          <Field name="password" type="password" className={styles.field} />
          <ErrorMessage name="password" component="div" className={styles.error} />
        </div>

        <button type="submit" className={styles.submitButton}>Submit</button>
      </Form>
    </Formik>
  );
};

export default LoginForm;
Enter fullscreen mode Exit fullscreen mode

Meanwhile, here’s an example using the styled-components library:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import styled from 'styled-components';

const StyledForm = styled(Form)`
  display: flex;
  flex-direction: column;
`;

const StyledField = styled(Field)`
  margin: 0.5rem 0;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
`;

const StyledErrorMessage = styled.div`
  color: red;
  font-size: 0.8rem;
`;

const SubmitButton = styled.button`
  padding: 0.5rem;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  &:hover {
    background-color: #0056b3;
  }
`;

const LoginForm = () => {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      validationSchema={Yup.object({
        email: Yup.string().email('Invalid email address').required('Required'),
        password: Yup.string().required('Required'),
      })}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          console.log(values);
          setSubmitting(false);
        }, 400);
      }}
    >
      <StyledForm>
        <label htmlFor="email">Email</label>
        <StyledField name="email" type="email" />
        <StyledErrorMessage><ErrorMessage name="email" /></StyledErrorMessage>

        <label htmlFor="password">Password</label>
        <StyledField name="password" type="password" />
        <StyledErrorMessage><ErrorMessage name="password" /></StyledErrorMessage>

        <SubmitButton type="submit">Submit</SubmitButton>
      </StyledForm>
    </Formik>
  );
};

export default LoginForm;
Enter fullscreen mode Exit fullscreen mode

You can also style Formik forms using Tailwind CSS. The code below shows how to do so:

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

const LoginForm = () => {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      validationSchema={Yup.object({
        email: Yup.string().email('Invalid email address').required('Required'),
        password: Yup.string().required('Required'),
      })}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          console.log(values);
          setSubmitting(false);
        }, 400);
      }}
    >
      <Form className="flex flex-col space-y-4">
        <div className="flex flex-col">
          <label htmlFor="email" className="mb-1">Email</label>
          <Field name="email" type="email" className="p-2 border border-gray-300 rounded" />
          <ErrorMessage name="email" component="div" className="text-red-500 text-sm mt-1" />
        </div>

        <div className="flex flex-col">
          <label htmlFor="password" className="mb-1">Password</label>
          <Field name="password" type="password" className="p-2 border border-gray-300 rounded" />
          <ErrorMessage name="password" component="div" className="text-red-500 text-sm mt-1" />
        </div>

        <button type="submit" className="p-2 bg-blue-500 text-white rounded hover:bg-blue-600">Submit</button>
      </Form>
    </Formik>
  );
};

export default LoginForm;
Enter fullscreen mode Exit fullscreen mode

You can also style Formik forms with a couple of third-party libraries. We’ll see some of them below. In this example, we use Formik with Material UI’s TextField and Button components:

import React from 'react';
import ReactDOM from 'react-dom';
import {
  useFormik
} from 'formik';
import * as yup from 'yup';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';

const validationSchema = yup.object({
  email: yup
    .string('Enter your email')
    .email('Enter a valid email')
    .required('Email is required'),
  password: yup
    .string('Enter your password')
    .min(8, 'Password should be of minimum 8 characters length')
    .required('Password is required'),
});

const WithMaterialUI = () => {
  const formik = useFormik({
    initialValues: {
      email: 'foobar@example.com',
      password: 'foobar',
    },
    validationSchema: validationSchema,
    onSubmit: (values) => {
      alert(JSON.stringify(values, null, 2));
    },
  });

  return ( <
    div >
    <
    form onSubmit = {
      formik.handleSubmit
    } >
    <
    TextField fullWidth id = "email"
    name = "email"
    label = "Email"
    value = {
      formik.values.email
    }
    onChange = {
      formik.handleChange
    }
    onBlur = {
      formik.handleBlur
    }
    error = {
      formik.touched.email && Boolean(formik.errors.email)
    }
    helperText = {
      formik.touched.email && formik.errors.email
    }
    /> <
    TextField fullWidth id = "password"
    name = "password"
    label = "Password"
    type = "password"
    value = {
      formik.values.password
    }
    onChange = {
      formik.handleChange
    }
    onBlur = {
      formik.handleBlur
    }
    error = {
      formik.touched.password && Boolean(formik.errors.password)
    }
    helperText = {
      formik.touched.password && formik.errors.password
    }
    /> <
    Button color = "primary"
    variant = "contained"
    fullWidth type = "submit" >
    Submit <
    /Button> <
    /form> <
    /div>
  );
};

ReactDOM.render( < WithMaterialUI / > , document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

The integration with Material UI allows us to use Material UI components and offers more granular control over validation, highlighting, and error display.

Similarly, we can use Formik with Semantic UI 2.0 like so:

import React from 'react';
import { Formik } from 'formik';
import { Form, Input, SubmitButton, ResetButton } from 'formik-semantic-ui-react';

export const LoginForm = (props: any) => {
  const initialValues = {
    email: '',
    password: '',
  };

  return (
    <div>
      <Formik
        initialValues={initialValues}
        onSubmit={ ()=>{ console.log('Form Submit' )} }
      >
        <Form size="large">
          <Input
            name="email"
            placeholder="Email"
            errorPrompt
          />
          <Input
            name="password"
            type="password"
            placeholder="Password"
            errorPrompt
          />
          <SubmitButton fluid primary>
            Login
          </SubmitButton>
          <ResetButton fluid secondary>
            Reset
          </ResetButton>
        </Form>
      </Formik>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

We used Semantic UI components like Form, Input, SubmitButton, and ResetButton. This example using Semantic UI with Formik shows how simple it is to integrate.

Many other third-party UI integrations are possible in Formik. You can check the Formik documentation directly for more details.

Further reading:

Comparing Formik to similar form handling tools for React

Let’s compare Formik to tools such as React Hook Form, Informed, react-ts-form, and traditional form management in React. The comparison will consider points such as features, performance, community, and documentation to help inform your choice:

Formik React Hook Form Informed react-ts-form
Features - State management: Keeps track of values, errors, and touched fields - Validation: Integrates with validation libraries; supports synchronous and asynchronous validation - UI libraries - Multi-step forms - State management: Uses uncontrolled components and ref-based API for minimal re-renders - Validation: Has built-in support using native HTML validation, custom validation, and integration with validation libraries - UI libraries - Multi-step forms: Recommended to use a state library to store user inputs - State management: Has a simpler API, including values, touched fields, errors, and dirty fields - Validation: Supports built-in validation, custom validation, and schema-based validation using Yup - Multi-step forms - Type safety: Strongly typed forms with TypeScript support - State management: With a focus on TypeScript integration - Validation: Built-in validation with support for schema-based validation using libraries like Yup - Form control: Granular control over form state and behavior, with a focus on type safety
Performance Controlled components can lead to more frequent re-renders. Managing form state internally can also be less performant for large forms. Handles complex form logic well, but may introduce performance overhead for extensive forms Highly optimized for performance with minimal re-renders and subscriptions. Also handles complex forms efficiently with better performance for large forms due to its architecture Efficient approach to state management minimizes re-renders. Optimized with better handling of large forms and complex states, focusing on performance and ease of use Built to work efficiently with TypeScript, ensuring minimal re-renders and type safety. Optimized for performance with better handling of large forms and complex states
Community Widely used and established in the React community. Actively maintained GitHub repo with many contributors, posts, and articles Gaining rapid popularity due to its performance benefits and modern approach. Active GitHub repo with many contributors, posts, and articles Gaining traction but not as widely adopted as Formik. Smaller community, but still active GitHub repo Gaining traction, especially among TypeScript users. Smaller but growing community, with an active GitHub repo and contributors
Resources - Documentation: Comprehensive and detailed, with examples and use cases - Tutorials: Numerous online tutorials, guides, and example projects - Official support: Maintained by the Formik team, which provides regular updates and improvements - Documentation: Extensive and well-organized, with clear examples and API references - Tutorials: Plenty of tutorials, guides, and example projects available online - Official support: Actively maintained with regular updates, improvements, and community engagement - Documentation: Clear and well-organized, with examples and API references - Tutorials: Fewer tutorials and example projects compared to Formik, but still sufficient for most use cases - Official support: Actively maintained with regular updates and enhancements - Documentation: Clear and well-organized, with examples and API references, particularly for TypeScript users - Tutorials: Fewer tutorials and example projects compared to Formik, but sufficient for most projects - Official support: Actively maintained with regular updates and enhancements.

This comparison table should serve as an efficient way to evaluate these form handling options at a high level. Keep in mind there are many other options you could consider as well; this is just a sample of some of the most popular.

Further reading:

Conclusion

Formik offers a reliable solution for managing forms of any size and complexity. Its comprehensive state management, smooth validation integration, and support for multi-step forms make it a performant and developer-friendly solution for React forms.

Using Formik in your React apps will significantly improve the way you handle forms, which will improve the productivity of your development process and the maintainability of your codebase.


Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.

NPM:

$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
Enter fullscreen mode Exit fullscreen mode

Script Tag:

Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Enter fullscreen mode Exit fullscreen mode

3.(Optional) Install plugins for deeper integrations with your stack:

  • Redux middleware
  • ngrx middleware
  • Vuex plugin

Get started now

💖 💪 🙅 🚩
leemeganj
Megan Lee

Posted on September 12, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related