Managing Form State in React With Redux Form

Yomi Eluwande
👁️ 10,205 views
💬 comments

redux-form is a great way of managing forms that are powered by Redux. It is a Higher-Order-Component (HOC) that uses react-redux to make sure HTML forms in React use Redux to store all of its state. In this tutorial, we'll see how to get started with redux-form by building a simple form with validation features.

Before getting started on using redux-form, let's go over a basic overview of redux-form and it's API.

Overview of redux-form

formReducer This is a function that tells how to update the Redux store based on changes coming from the application; those changes are described by Redux actions. formReducer has to be mounted on the Redux state at form.

Table of Contents

    reduxForm() The reduxForm() function is a higher-order component takes a configuration object and it always returns a new function. It is used to wrap the form component and bind user interaction to the Redux dispatch actions.

    Field The <Field/> component is a component that lives inside your wrapped form component. It serves as a way to connect the input elements in a form to the redux-form logic. Simply put, it's the way we get the input from what users type.

    You can read more about redux-form API at the documentation page.

    Let's build the form now.

    Build a form with redux-form

    We'll be building a React app with the create-react-app package. create-react-app allows you to create React apps with no build configuration. You can use create-react-app by running the terminal command below. It automatically creates a React app for you in a folder titled contact-redux.

    npx create-react-app contact-redux

    If you're wondering what npx is, it's a new tool that's intended to help with installations of packages from the npm repository. It's a package runner that makes it super easy to install and manage dependencies hosted on the registry. It's important to note that npx only works with versions of npm that are 5.2 and above. If you have a version lower than that and would still like to use create-react-app on your computer. Run the terminal commands below to install create-react-app and start a React app.

    npm install -g create-react-app
    create-react-app contact-redux

    Once the React app has been setup, you can navigate to the directory and start the dev server to see if everything works. Run the npm start command to start the newly created React app in development mode.

    We now have a React app up and running so let's begin to add the dependencies we'll need for the form.

    npm i redux react-redux redux-form
    • redux - A state container and it's a prerequisite for redux-form to work.
    • react-redux - React Redux is the official React bindings for Redux and it's also a prerequisite for redux-form to work
    • redux-form - The package in use for this tutorial.

    Once that's been installed, we can then begin to work on the contact form. We'll add a Bulma CDN link to the index.html file so as to add some default styling. Open the public/index.html file and add the following line of code to the head tag.

        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">

    We'll be making some edits to the src/App.js file now. Open up the src/App.js file and add the line of code below at the top of the file.

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

    Creating the Form

    Next, go to the render() function, edit it with the following.

    render() {
        return (
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <h1 className="App-title">Welcome to React x redux-form</h1>
            </header>
            <div className="container">
              <p className="App-intro">
                Contact Form
              </p>
              <SignInForm />
            </div>
          </div>
        );
      }

    The introductory text has been changed and most importantly we added a <SignInForm /> component which we'll create below. It's going to be a simple component that returns the form we need and it will be hooked up to redux-form component. In the same src/App.js file, type out this code below just before the declaration of class App extends Component.

    let SignInForm = props => {
      return <form className="form">
        <div className="field">
          <div className="control">
            <label className="label">First Name</label>
            <Field className="input" name="firstName" component="input" type="text" placeholder="First Name"/>
          </div>
        </div>
    
        <div className="field">
          <div className="control">
            <label className="label">Last Name</label>
            <Field className="input" name="lastName" component="input" type="text" placeholder="Last Name"/>
          </div>
        </div>
    
        <div className="field">
          <div className="control">
            <label className="label">Email</label>
            <Field className="input" name="email" component="input" type="email" placeholder="Email Address"/>
          </div>
        </div>
    
        <div className="field">
          <div className="control">
            <label className="label">Proficiency</label>
            <div className="select">
              <Field className="input" name="proficiency" component="select">
                <option />
                <option value="beginner">Beginner Dev</option>
                <option value="intermediate">Intermediate Dev</option>
                <option value="expert">Expert Dev</option>
              </Field>
            </div>
          </div>
        </div>
    
        <div className="field">
          <div className="control">
            <label className="label">Age</label>
            <Field className="input" name="age" component="input" type="number" placeholder="Age"/>
          </div>
        </div>
    
        <div className="field">
          <div className="control">
            <label className="label">Gender</label>
            <label className="radio">
              <Field name="gender" component="input" type="radio" value="male" />
              {' '}
              Male
            </label>
            <label className="radio">
              <Field name="gender" component="input" type="radio" value="female" />
              {' '}
              Female
            </label>
          </div>
        </div>
    
        <div className="field">
          <div className="control">
            <label className="checkbox">
              <Field name="saveDetails" id="saveDetails" component="input" type="checkbox"/>
              Save Details
            </label>
          </div>
        </div>
    
        <div className="field">
          <div className="control">
            <label className="label">Message</label>
            <Field className="textarea" name="message" component="textarea" />
          </div>
        </div>
    
        <div className="field">
          <div className="control">
            <button className="button is-link">Submit</button>
          </div>
        </div>
    
      </form>;
    };

    In the code block above, we set up a basic contact form, it asks the user for information such as First Name, Last Name, Age, Gender, e.t.c. The interesting bit in this form is the Field component. The Field component is gotten from the redux-form package and it's how we write the input field. The type prop indicates what type of input it should be, that is, a radio input, a checkbox input, a text input or an email input. The component prop determines what type of input field it should be, it could be input, textarea or select tags and the name prop is what will be used to identify the state of the fields in the redux store which we'll create below.

    So in order to use the form hooked up to redux-form, we need to have some sort of Redux store created already and that's what we're going to do next.

    Setup a Redux Store

    We need a redux store in which we can connect the form component (SignInForm) we created. Let's start by importing the redux package. Open the src/index.js file and add the following lines of code in which we are basically importing redux to the React app.

    import { createStore, combineReducers } from 'redux';
    import { Provider } from 'react-redux';
    import { reducer as formReducer } from 'redux-form';

    The first line of code imports createStore and combineReducers. createStore helps to create a Redux store that holds the complete state tree of your app and the combineReducers helps to manage all of your reducer functions into one single helper function which can then be passed into createStore. You can read more about these function on the Redux API reference page.

    The second line of code imports Provider from react-redux. Provider helps to pass the state of the store to all container components in the app and we'll demonstrate how that works later.

    The third line of code imports reducer as formReducer and that's what we're going to use to hook up our form to the Redux store.

    Next, we'll create the actual Redux store and make sure it's applicable to all container components by using the Provider component. Edit the src/index.js file with the following code block.

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    import { createStore, combineReducers } from 'redux';
    import { Provider } from 'react-redux';
    import { reducer as formReducer } from 'redux-form';
    
    import './index.css';
    import App from './App';
    import registerServiceWorker from './registerServiceWorker';
    
    const rootReducer = combineReducers({
      form: formReducer,
    });
    
    const store = createStore(rootReducer);
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    
    registerServiceWorker();

    In the code block above, we use the combineReducers function to connect the formReducer from the form to the Redux store. It's basically used to update any of our state in response to actions, which in this case, are changes to the form. The next line of code is used to create a store by using createStore from Redux.

    This newly created store is then made available to all parts of the app with the help of Provider which is wrapped around the App component and it also accepts a prop of store which is the store that was created above.

    Let's go back to the form and finally connect it to the store.

    Connecting the Form to redux-form

    We have our form component but it's not connected to redux-form yet. Let's fix that. Type out this code block below just before the class App extends Component and immediately after the declaration of the SignInForm presentational component.

    SignInForm = reduxForm({
      form: 'signIn',
    })(SignInForm);

    In the code block above, SignInForm is made into a redux-connected form using the reduxForm Higher Order Component. This means that our form is now hooked up to the store. One thing to note is the config key form, it is used as an identifier and it's used to provide a unique name for the form component. If they were multiple forms, then you'd need to use separate names so as to better manage their different states.

    The next thing we have to do is configure what happens when we click on the Submit button. In an ideal app, you'd want to send data to a remote API or some databases but for the purpose of demonstrations, we'll simply log the form data into the browser console. To do that we'll need to get the form data from the props and store them somewhere.

    Inside the SignInForm component, add the line of code below just above the return statement.

      const { handleSubmit } = props;
      return <form **onSubmit={handleSubmit}** className="form">

    The props in the SignInForm form is destructured into handleSubmit. The handleSubmit function will then be used in the form as a handler for the onSubmit event when the submit button is clicked on.

    Finally, inside the App component in the src/App.js file, we'll create a function that logs the form data to the browser console. Add the code block below to the file just before the render() function.

        handleSignIn = values => {
            console.log(values);
        };

    Then add the handleSignIn function as an event handler for the SignInForm component. redux-form automatically will ascertain that the data gotten from the form, which is essentially the SignInForm component should be logged to the console thanks to the handleSubmit function above.

        <SignInForm onSubmit={this.handleSignIn} />

    You can now start the app by running the npm start terminal command. Fill out the form, click on submit and you should the values logged to the console.

    Adding Validations

    Validations are important when it comes to building forms and redux-form ships with some validation features and we're going to implement that now. In the src/App.js file, type in the code block below.

    const validate = val => {
      const errors = {};
      if (!val.firstName) {
        console.log('First Name is required');
        errors.firstName = 'Required';
      }
      if (!val.lastName) {
        console.log('Last Name is required');
        errors.lastName = 'Required';
      }
      if (!val.email) {
        console.log('email is required');
        errors.email = 'Required';
      } else if (!/^.+@.+$/i.test(val.email)) {
        console.log('email is invalid');
        errors.email = 'Invalid email address';
      }
      if (!val.age) {
        errors.age = 'Required'
      } else if (isNaN(Number(val.age))) {
        errors.age = 'Must be a number'
      } else if (Number(val.age) < 18) {
        errors.age = 'Sorry, you must be at least 18 years old'
      }
      return errors;
    };

    The validate function is used to check for validation errors in the form. The val parameter will be used to check the validation of different fields. We first check if the errors object is empty, an empty object obviously means there are no errors. We then use conditional logic to check if a field is empty, if it is, you throw the corresponding error. In the code block above, we're only doing validations for firstName, lastName, email, and age. In the email validation conditional, we check if it's empty and also check if it's a valid email by using regex and in the age validation conditional, we check if it's empty, a number and if the user is less than 18.

    Next, we'll register our validation function to redux-form so that it can begin using it to carry out validation tests. Add the validate function to the redux-form Higher Order Component as seen below.

    SignInForm = reduxForm({
      form: 'signIn',
      validate,
    })(SignInForm);

    Now that we have our validation function ready and registered in the redux-form HOC, how do we use it in our forms? Simple. By creating a reusable component that displays errors whenever there are any and using that newly created component in our forms.

    const renderField = ({ input, label, type, meta: { touched, error, warning } }) => (
      <div>
        <div className="control">
          <label className="field">{label}</label>
          <input className="input" {...input} placeholder={label} type={type}/>
          {touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))}
        </div>
      </div>
    )

    The renderField component takes in props of the input object, a label, the type of input and meta which is a redux-form property. The line {touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))} simply means that an error message should show if there are any errors when the form field has been clicked/focused on. Also, the form will not be submitted if there are any errors.

    Now, if you check the form and try to enter any invalid input or skip the fields that have validation tests, you should error messages underneath the form.

    Conclusion

    In this tutorial, we've seen how to build forms with redux-form and make it connected to the Redux store. We also saw how to implement synchronous validation on the form without the need for external schema validators or anything.

    You can read more about redux-form here and you can go through their several examples and learn how redux-from can be helpful for your next project.

    The codebase for this tutorial can be seen on GitHub.