article

Integrating Formik and Redux: complex forms made easy

If you are a React developer you have probably had some rough times dealing with forms, especially the more intricate ones. Managing forms in React can be a hassle when handling input values, validation, error messages, and form submission - It can become an extremely tedious task. There are a few libraries that will help you deal with forms, such as Redux-Form and Formik. For its simplicity, independence of other libraries, and efficiency, we chose to work with Formik in one of our projects.

However, we also use Redux in the same project to manage states so each component can access any state they need from the store. This way, we can have consistent behavior throughout the whole application. This does, however, raise a concern: how can we properly integrate Formik with Redux to manage forms easily and simply?

Formik standardizes input components and the flow of the form submission. To make it easier for us to understand how it works, let’s check an example taken from the Formik documentation:

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

const Basic = () => (
  <div>
    <h1>Login Form</h1>
    <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>
);
export default Basic;

As you might have noticed, Formik has a few built-in components that help us on our journey to managing forms in React, such as Formik, Form, Field, ErrorMessage. <Formik /> component is the wrapper equipped with numerous props to help us build events; <Form /> is a wrapper around an HTML <form> element that is automatically hooked into Formik’s handleSubmit and handleReset; <Field /> uses the name attribute to automatically hook up inputs to Formik.

The trick is in the built-in props we mentioned before; they are really handy when validating fields and managing forms states. Many props within Formik might be useful for you and you can check them here, but our goal here is to integrate our form with Redux and for that we will only focus on the handleSubmit prop for now.

First of all, let’s organize our form better separating the component we are building from the Formik code.

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

class Basic extends React.Component {
  render() {
    return (
      <div>
        <h1>Login Form</h1>
        <form>
          <input type="email" name="email" />
          <input type="password" name="password" />
          <button type="submit" disabled={isSubmitting}>
            Submit
              </button>
        </form>
      </div>
    )
  }
};

const Form = withFormik({
  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;
  },
  handleSubmit(values, { props, setSubmitting }) {
          setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 400);
  },
})(Basic)

export default Form;
Way simpler, huh?

We still have to integrate the component itself with Formik using Redux. In the next steps, it’s important to understand how Redux works. If you’re not familiar yet, I would suggest taking a look at the Redux documentation.

1. First, the request for our API would look something like this:

export default class User {
  static signIn({ email, password }) {
     return api('/auth/login', {
       json: true,
       headers: {
    	... /* Here you handle the header of your request */
       }
    }).then((res) => res.body)
  }
}

2. Then let’s add our actions and its action creators:

export function loginSucceeded(user) {
  return {
    type: 'LOGIN_SUCCEEDED',
    user
  }
}

export function requestLogin(user) {
  return {
    type: 'LOGIN_REQUESTED',
    user
  }
}

export function loginFailed(user) {
  return {
    type: 'LOGIN_FAILED',
    user
  }
}

3. Last but not least, let’s create a function to wrap it all:

export const logInUser = (user) => {
  return (dispatch) => {
    dispatch(requestLogin(user))
    return signIn(user)
      .then((res) => {
        dispatch(loginSucceeded(res))
         return res
      })
      .catch((err) =>{
        dispatch(loginFailed(user))
    })
  }
}

4. Now that we have our Redux properly set up and ready to be used, let’s integrate it to our form:

import React from 'react';
import { connect } from 'react-redux'
import { bindActionCreators } from 'react-redux'
import { withFormik } from 'formik'


class Basic extends React.Component {
  render() {
    const {
      errors,
      handleSubmit,
      isSubmitting,
      values,
      setFieldValue,
      setFieldTouched,
    } = this.props

    return (
      <div>
        <h1>Login Form</h1>
        <form onSubmit={handleSubmit}>
          <input 
           value={values.email} 
           type="email" name="email" 
           onChange={setFieldValue} 
           onBlur={setFieldTouched}
          />
          <input
           value={values.password}
           type="password" 
           name="password" 
           onChange={setFieldValue} 
           onBlur={setFieldTouched}
          />
          <button type="submit" disabled={isSubmitting || errors}>
            Submit
          </button>
        </form>
      </div>
    )
  }
};

const Form = withFormik({
  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;
  },
  handleSubmit(values, { props, setSubmitting }) {
    const { logInUser } = props
    const payload = { email: values.email, password: values.password }
    logInUser(payload).then(() => setSubmitting(false))
  },
})(Basic)

const mapDispatchToProps = (dispatch) => bindActionCreators({
  logInUser
}, dispatch)

Following the steps above:

In 1, we created a basic authentication request to our API.

After that, in 2, we created our actions to help us handle the data we send to the Redux store.

In 3, we wrap it all up in one function, so wherever we need to call it in our application, it’ll be just as simple as this.

And finally, in 4, we map our function to props with mapDispatchToProps so that we can use it within our component. We also implemented the handleSubmit from Formik properly and we just added some of the Formik built-in methods to help us handle our inputs, such as setFieldValue, that will, among other things, trigger the validation for the input value, for example.

The integration is pretty simple. We chose this approach to our project because it becomes easily maintainable as we decouple logic from our form component itself. We keep everything separated, with its concern. This integration is particularly useful when dealing with more complex forms but still eases our lives even in the simpler ones.

What is your favorite way of dealing with forms in React? Do you handle Formik and Redux differently? Do you think I missed something important? Please feel free to reach me or leave a comment. I’d be glad to hear your thoughts!

David Pierre

Software Developer at Vinta Software.