Heading image for post: Get Started with redux-form

Get Started with redux-form

Profile picture of Josh Branchaud

The redux-form library bills itself as the best way to manage your form state in Redux. It provides a higher-order form component and a collection of container components for dealing with forms in a React and Redux powered application. Most importantly, it makes it easy to get a form up and running with state management and validations baked in.

redux-form sign in

To get a feel for what redux-form can do for us, let's build a simple Sign In form. You can follow along below or check out the resulting source code directly.

Start with React

We are going to start by getting the React portion of this application setup. The easiest way to spin up a React app is with create-react-app. Install it with npm or yarn if you don't already have it globally available.

$ yarn global add create-react-app

Let's generate our project.

$ create-react-app redux-form-sign-in

The create-react-app binary that is now available on our machine can be used to bootstrap a React app with all kinds of goodies -- live code reloading in development and production bundle building -- ready to go.

Let's jump into our project and kick off a live reloading development server.

$ cd redux-form-sign-in
$ yarn start

At this point you should see your browser pointed to localhost:3000 with a page reading Welcome to React.

Is This Thing Plugged In?

We can see the live-reload in action by altering src/App.js.

    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Redux Form Sign In App</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );

Change the text in the h2, save the file, and then switch back to the browser to see the changes almost instantly.

create-react-app made it really easy to get to a point where we can just iterate on our app. We didn't have to fiddle with Webpack configurations or any other project setup.

Next, let's change the prompt in the app intro.

        <p className="App-intro">
          Sign in here if you already have an account
        </p>

Our app is now begging for a form which brings us to redux-form, the focus of this post.

Satisfying Some Dependencies

Let's add the redux-form dependency to our project.

$ yarn add redux-form

It is now available to our app, so we can import the reduxForm function at the top of src/App.js right after the react import.

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

If we save the file, our development server will have a complaint for us regarding an unmet dependency.

Module not found: Can't resolve 'react-redux' in '/Users/dev/hashrocket/redux-form-sign-in/node_modules/redux-form/es'

The same issue will arise for the dependency on redux itself. So, you'll want to add both to the project.

$ yarn add react-redux redux

Trigger a reload by saving and you'll see that the code is successfully compiling again. Not without warnings though. reduxForm is never being used, so let's use it.

A Basic Form

The reduxForm function is the higher-order component (HOC) that creates our super-charged form component. In particular, it wires this component up to redux for management of our form's state.

First, let's add an empty form in a presentational component.

// src/App.js
let SignInForm = props => {
    return <form />;
};

Then, let's transform it into a redux-connected form using the reduxForm HOC.

// src/App.js
SignInForm = reduxForm({
  form: 'signIn',
})(SignInForm);

The only required configuration property is form which specifies a unique name for the form component. If you wanted to create multiple instances of the same form on a page, each would need a separate name in order to manage their separate form states.

Let's render our new form component into our existing React app.

// src/App.js
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Redux Form Sign In App</h2>
        </div>
        <p className="App-intro">Sign in here if you already have an account</p>
        <SignInForm />
      </div>
    );

Saving results in successful compilation, but there is a runtime error. The details of which are displayed in your browser.

A Redux Store

The first part of the error reads:

Could not find "store" in either the context or props of "Connect(Form(SignInForm))".

We are missing a redux store to which reduxForm can connect our form component. The store is the place where redux will manage our form's state. The error helpfully suggests two ways to address this problem. Let's do the first and add a provider to src/index.js.

// src/index.js
// ... leave existing import statements intact
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';

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

const store = createStore(rootReducer);

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

The Provider is a component from react-redux that makes our store available to child components. The exact details are abstracted away by redux-form. The react-redux docs have more details if you are interested.

The Provider requires one prop, the store. We set up our store with createStore from redux. createStore is given a top-level reducer that it can use to update any of our state in response to actions, such as changes to the form. We construct this top-level reducer, rootReducer, using combineReducers. Don't worry if you are not familiar with Redux's core concepts, that is the extent of what we need to know for this post. If you want to know more I do highly recommend Dan Abramov's course, 'Getting Started with Redux'.

Save these changes and our form will render successfully. Because we didn't add any form elements yet, you'll have to inspect the DOM with dev tools to confirm that.

A Form with Fields

A form without any fields isn't much of a form. So, let's add some with the Field component provided by redux-form. Because this is a Sign In form, we'll need an Email field, a Password field, and a submit button to sign in.

// src/App.js
let SignInForm = props => {
  return (
    <form>
      <label>Email</label>
      <div>
        <Field type="text" name="email" component="input" />
      </div>
      <label>Password</label>
      <div>
        <Field type="password" name="password" component="input" />
      </div>
      <button type="submit">Sign in</button>
    </form>
  );
};

The label and button parts of the above JSX are pretty standard parts of a form. The interesting bit is the Field component. For each we specify the component prop as input because these, for the time being, are both simply input tags. The type prop gets passed down to each of the inputs specifying one as a standard text input and the other as a password input. The name is important because it will be used to identify the state of the fields in the redux store.

Our app won't recognize the Field component until we import it, so let's update this statement at the top of the same file.

// src/App.js
import { reduxForm, Field } from 'redux-form';

If we save our file and check out the re-rendered page, we'll see our form. Go ahead and inspect the DOM to see how the Field components were translated to <input> tags.

Unfortunately, this form isn't much to look at right now. I'll leave styling it as an exercise for the reader.

Form State

Clicking our Sign in button at this point isn't all that satisfying. We want to know that the values that have been entered into our form fields are being managed by redux. Let's pass down a callback function that our form can call when it is submitted.

// src/App.js
  handleSignIn = values => {
    console.log('Submitting the following values:');
    console.log(`Email: ${values.email}`);
    console.log(`Password: ${values.password}`);
  };

This function will simply spit out the form values with console.log. This is more or less where you'd want to integrate the app to some backend system. We won't deal with a backend in this post.

If we give this handler function to our redux form, SignInForm, via the onSubmit prop, it will be available to us in the props of our form component.

// src/App.js
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Redux Form Sign In App</h2>
        </div>
        <p className="App-intro">Sign in here if you already have an account</p>
        <SignInForm onSubmit={this.handleSignIn} />
      </div>
    );
  }

The callback function that we passed in to our form is wrapped in the handleSubmit function. This can be destructured from our form's props and passed in to the onSubmit of our <form>.

// src/App.js
  const { handleSubmit } = props;
  return (
    <form onSubmit={handleSubmit}>
      // ...
    </form>
  );

Enter in a value for Email and Password and hit submit. You should see those values in the console output. Awesome!

We don't want our form to allow just any values though.

Do You Validate?

redux-form comes with built-in support for both synchronous and asynchronous validations. To start, let's add synchronous validations to require both the email and password; that is, we won't allow blank values. This can be done by defining a validate function.

// src/App.js
const validate = values => {
  const errors = {};

  if (!values.email) {
    console.log('email is required');
    errors.email = 'Required';
  }

  if (!values.password) {
    console.log('password is required');
    errors.password = 'Required';
  }

  return errors;
};

This function starts with errors as an empty object. If the function ultimately returns an empty object, then there were no errors and the form is valid. In fact, if you don't define the validate function, it defaults to (values, props) => ({}) -- always valid.

The conditional logic in the middle of our function is what decides whether to flag any validation errors. Our current implementation checks for the presence of each field. Additionally, we console.log every time a validation check fails so that we can see it working as we develop.

To override the default implementation, we have to tell SignInForm to use our implementation. This is done in the form constructor using es6's object literal property shorthand.

// src/App.js
SignInForm = reduxForm({
  form: 'signIn',
  validate,
})(SignInForm);

If you open up the dev tools console and then try typing into the fields, you'll notice that every single change to the form triggers the validate function. This ensures that we always have fresh information about the validity of our form. It also means that our form defaults to invalid. As such, we need to be discerning in how and when we display those validation errors. Metadata provided to our Field component allows us to do that as we'll see in the next section.

Additionally, we may want a validation that ensures the value entered into the email field looks like an email address. To achieve that, we can update our validate function like so.

  if (!values.email) {
    console.log('email is required');
    errors.email = 'Required';
  } else if (!/^.+@.+$/i.test(values.email)) {
    console.log('email is invalid');
    errors.email = 'Invalid email address';
  }

It is a rather crude check of the email address field, but it will suffice for this post. Our validate function will now ensure that even if the email is present that it still conforms to the email address regex we've laid out.

As long as there are validation errors on the errors object, we will not be able to submit the form. That is, our handleSignIn function will not be triggered. But beyond that and our console.log statements, there is no outward expression of the validation errors.

Displaying Errors

Earlier on I hinted that using input as the component for Field was only temporary. Let's extract a functional component with logic for displaying our errors. We can use that custom component, instead of input, with Field.

// src/App.js
const InputField = ({
  input,
  label,
  type,
  meta: { touched, error, warning },
}) =>
  <div>
    <label>
      {label}
    </label>
    <div>
      <input {...input} type={type} />
    </div>
  </div>;

The props passed into InputField include the input object, a label which we will need to specify, the type of input it is (e.g. password), and some redux-form specific metadata.

Using InputField, we are able to greatly simplify and dry up the JSX in SignUpForm.

// src/App.js
  return (
    <form onSubmit={handleSubmit}>
      <Field type="text" name="email" component={InputField} label="Email" />
      <Field
        type="password"
        name="password"
        component={InputField}
        label="Password"
      />
      <button type="submit">Sign in</button>
    </form>
  );

We are now in a position to decide how we want to render our errors. Better yet, the JSX for it only needs to be defined in one place, inside the InputField component.

// src/App.js
const InputField = ({
  input,
  label,
  type,
  meta: { touched, error, warning },
}) =>
  <div>
    <label>
      {label}
    </label>
    <div>
      <input {...input} type={type} />
    </div>
    {touched &&
      error &&
      <div>
        {error}
      </div>}
  </div>;

We can render a div with the error message when there is an error. That isn't quite good enough though. Remember when I mentioned being discerning about when to render errors. The user experience of seeing an error the second the page loads is not great. Fortunately, redux-form lets us know through the touched flag in the metadata if a particular field has been focused and then blurred. By combining touched and the presence of an error we are able to display an error message only when necessary.

Check it out in the browser to see the various validation messages we programmed into our form.

Conclusion and Next Steps

That's it. We made it. A lot was covered in this post. Having gone all the way through, you should now have a solid foundation for getting redux-form set up with create-react-app, building out a simple form, adding validations, and responding to a form submission.

redux-form is capable of a lot more than what we have explored in this post. There are a number of examples on their site that I'd recommend checking out to see what is available.

Looking for a next step? Try connecting the form to a backend system. Then, see if you can get Async Form Validation working.


Cover image by Steinar Engeland on Unsplash.

More posts about Javascript react redux-form