Form Handling Using Redux Form

Handling the data and events related to forms and input fields in web apps is not most people’s idea of fun. Thankfully though, there are plenty of available libraries that abstract away some of the tedious work. If your React app already happens to use Redux for state management, then Redux Form is one such option. It makes managing your app’s form state easy and straightforward.

Let’s go over just enough of the basics to get you up and running creating and handling form data.

Installation & Setup

Go ahead and add the redux-form package to your project:

$ yarn add redux-form

# or, using npm:
$ npm install redux-form

Then you’ll add a reducer named form that points to Redux Form’s reducer. That’ll probably mean that your app will have more than one reducer, so you can use something like the following with the combineReducers function to create a root reducer with all your app’s reducers:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import todoReducer from './reducers';

import App from './App';

const rootReducer = combineReducers({
  todo: todoReducer,
  form: formReducer
});

const store = createStore(rootReducer, applyMiddleware(thunk));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Note that the name of the form reducer is important and should be kept to form.

And with this, you’re ready to start defining forms and have their state entirely managed by Redux Form.

Usage

Let’s build a simple form that we’ll call GatorForm step by step. As you’ll see, we’ll make use of two exports from Redux Form: the reduxForm function and the Field component. First, just an input field with its label and a submit button:

components/GatorForm.js

import React from 'react';
import { reduxForm, Field } from 'redux-form';

const GatorForm = ({ handleSubmit }) => {
  return (
    <form onSubmit={handleSubmit(val => console.log(val))}>
      <label htmlFor="first-name">Your first name:</label>
      <Field
        name="firstName"
        type="text"
        component="input"
        id="first-name"
        placeholder="Benedict"
      />
      <button type="submit">Submit</button>
    </form>
  );
};

export default reduxForm({
  form: 'gatorForm'
})(GatorForm);

There isn’t much to our form so far, but we can already draw some lessons:

  • The reduxForm function is used to return a higher-order component that is augmented will all kinds of props that relate to our form. It expects a config object with at least a unique form name.
  • We make use of one of the callback props available on the HOC, handleSubmit. It can be used like in our example with a callback function that will be called when the form is submitted and that will receive the form values. It can also be used without the callback, onSubmit={handleSubmit}, provided that the component that uses the form has an onSubmit callback prop, which will be called. The form won’t be submitted if validation fails.
  • The Field component is used to add an input field. Here we used the input component of type text. All regular form fields are available. For example, for an input component, the type could have been number, email, password, radio or checkbox. The component prop also accepts the textarea and select string values as well as custom components.
  • The name given to a field is important because it’ll be used when accessing the form’s values. Here for example our field value will come up under firstName.

There are a ton more props available from the HOC, notable ones include warning, pure, pristine, reset, error, anyTouched and dirty.

Let’s add a little bit to our form and learn a little bit more about the available props at the same time:

components/GatorForm.js

import React from 'react';
import { reduxForm, Field } from 'redux-form';

const foods = ['pizza', 'tacos', 'nachos', 'hot dogs'];

const GatorForm = ({ handleSubmit, reset, pristine, submitting, valid }) => {
  return (
    <form onSubmit={handleSubmit(val => console.log(val))}>
      <label htmlFor="first-name">Your first name:</label>
      <Field
        name="firstName"
        type="text"
        component="input"
        id="first-name"
        placeholder="Benedict"
      />
      <label htmlFor="food-choice">Food choice</label>
      <Field name="foodChoice" id="food-choice" component="select">
        <option />
        {foods.map(food => {
          return (
            <option key={food} value={food}>
              {food}
            </option>
          );
        })}
      </Field>
      <button type="submit" disabled={!valid || pristine || submitting}>
        Submit
      </button>
      <button type="button" onClick={reset}>
        reset
      </button>
    </form>
  );
};

export default reduxForm({
  form: 'gatorForm'
})(GatorForm);

Now our form can be reset and the submit button will be disabled when the form is not valid, when it’s pristine or when it’s in the process of submitting. We also added a select field with a few options populated from an array of available options.

Custom Field Components

For maximum flexibility, it’s just as easy to provide custom components to the Field component. This becomes especially useful to add additional markup for validation messages. Below we use a function that returns a component that includes an input field and markup for eventual validation errors:

import React from 'react';
import { reduxForm, Field } from 'redux-form';

const newField = ({
  input,
  type,
  placeholder,
  id,
  meta: { touched, error }
}) => {
  return (
    <div>
      <input {...input} placeholder={placeholder} type={type} id={id} />
      {touched && error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
};

const GatorForm = ({ handleSubmit, reset, pristine, submitting, valid }) => {
  return (
    <form onSubmit={handleSubmit(val => console.log(val))}>
      <label htmlFor="first-name">Your first name:</label>
      <Field
        name="firstName"
        type="text"
        component={newField}
        id="first-name"
        placeholder="Benedict"
      />
      <label htmlFor="email">Email:</label>
      <Field
        name="email"
        type="email"
        component={newField}
        id="email"
        placeholder="benedict@alligator.io"
      />
      <button type="submit" disabled={!valid || pristine || submitting}>
        Submit
      </button>
      <button type="button" onClick={reset}>
        reset
      </button>
    </form>
  );
};

export default reduxForm({
  form: 'gatorForm'
})(GatorForm);

As you can see, the function that returns the component receives a bunch of useful props. It’s important here to spread the input props ({...input}) so that the input field receives all the event handling callback props. We can also access the other static props used with the Field component.

As you can also see, we’ve set the stage for validation and displaying error messages. We first check that the field has been touched (focused in and out) to ensure that no error message is shown on an untouched input field.

Validation

One of the most important tasks when it comes to form handling on the front-end is proper validation so that the UX can feel intuitive and users are properly guided towards correctly filling the form. Thankfully, Redux Form also makes validation easy and supports both synchronous and asynchronous validation. Here we’ll have a look at how to perform synchronous validation.

Here’s how you’d perform validation on the form level:

import React from 'react';
import { reduxForm, Field } from 'redux-form';

const newField = ({
  input,
  type,
  placeholder,
  id,
  meta: { touched, error },
  ...rest
}) => {
  return (
    <div>
      <input {...input} placeholder={placeholder} type={type} id={id} />
      {touched && error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
};

const GatorForm = ({ handleSubmit, reset, pristine, submitting, valid }) => {
  return (
    <form onSubmit={handleSubmit(val => console.log(val))}>
      <label htmlFor="first-name">Your first name:</label>
      <Field
        name="firstName"
        type="text"
        component={newField}
        id="first-name"
        placeholder="Benedict"
      />
      <label htmlFor="email">Email:</label>
      <Field
        name="email"
        type="email"
        component={newField}
        id="email"
        placeholder="benedict@alligator.io"
      />
      <button type="submit" disabled={!valid || pristine || submitting}>
        Submit
      </button>
      <button type="button" onClick={reset}>
        reset
      </button>
    </form>
  );
};

const myValidator = values => {
  const errors = {};
  if (!values.firstName) {
    errors.firstName = 'First name is required';
  } else if (values.firstName.length < 3) {
    errors.firstName = "Your name can't be that short!";
  }
  if (!values.email) {
    errors.email = 'Hold on a minute, we need an email!';
  } else if (!/(.+)@(.+){2,}\.(.+){2,}/i.test(values.email)) {
    // use a more robust RegEx in real-life scenarios
    errors.email = 'Valid email please!';
  }
  return errors;
};

export default reduxForm({
  form: 'gatorForm',
  validate: myValidator
})(GatorForm);

You pass a validator function under the validate key of the reduxForm function. That function receive the form values and should return an empty object for a valid form or an object with field names and an error messages when there are validation errors. As you saw previously, it’s then easy in a custom field component to access and display error messages.


Similarly, you can perform validation on the field level by using a validate prop on the Field and passing individual validation functions that receive the current value. Return undefined when there’s no error or an error message string when there’s an error:

import React from 'react';
import { reduxForm, Field } from 'redux-form';

const newField = ({
  input,
  type,
  placeholder,
  id,
  meta: { touched, error },
  ...rest
}) => {
  return (
    <div>
      <input {...input} placeholder={placeholder} type={type} id={id} />
      {touched && error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
};

const required = value => (value ? undefined : 'Required!');
const longEnough = value =>
  value && value.length >= 3 ? undefined : 'Too short!';
const email = value =>
  value && /(.+)@(.+){2,}\.(.+){2,}/i.test(value)
    ? undefined
    : 'Invalid email!';

const GatorForm = ({ handleSubmit, reset, pristine, submitting, valid }) => {
  return (
    <form onSubmit={handleSubmit(val => console.log(val))}>
      <label htmlFor="first-name">Your first name:</label>
      <Field
        name="firstName"
        type="text"
        component={newField}
        id="first-name"
        placeholder="Benedict"
        validate={[required, longEnough]}
      />
      <label htmlFor="email">Email:</label>
      <Field
        name="email"
        type="email"
        component={newField}
        id="email"
        placeholder="benedict@alligator.io"
        validate={[required, email]}
      />
      <button type="submit" disabled={!valid || pristine || submitting}>
        Submit
      </button>
      <button type="button" onClick={reset}>
        reset
      </button>
    </form>
  );
};

export default reduxForm({
  form: 'gatorForm'
})(GatorForm);

⛵ May the winds of form handling always be at your back! Refer to the API docs to dig deeper into the possibilities.

  Tweet It

🕵 Search Results

🔎 Searching...