Improving React forms with Redux Form – Visionmate – A full-service digital partner

Improving React forms with Redux Form

Intro

Redux is a great tool to dramatically improve data management in your application. Therefore, a natural step would be to use it also for your application forms. This would work very well, but why reinvent the wheel?

Redux Form does exactly how you would expect – it uses application state to manage form data without the necessity to write all the code by yourself.

In this article, I’d like to go through the process of setting up Redux Form and will provide a few examples of typical cases.

Setup

Redux Form setup includes the following steps:

Include form reducer in combineReducers function (this needs to be done once for all forms in your application)

import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({  
/* Other reducers */  
    form: formReducer
});
export default rootReducer;

Link selected form to form reducer (let’s say you have a component called LoginPage)

import {reduxForm} from "redux-form";

export default reduxForm({ 
  form: 'login' /* This must be unique */ 
})(LoginPage);

In the component itself, you need to link your form to a function, which will be executed upon submitting it. This is done via ‘handleSubmit’ handler supplied by Redux Form.

const {handleSubmit} = props
return (
    <form onSubmit={handleSubmit(login)}>
      <Field
        name='username'
        placeholder = "Enter username"
        component="input"type='text'
      />
      <Field
        name='password'
        placeholder = "Enter password"
        component="input"
        type='password'
      />
      <button type="submit">Login</button>
    </form>
)

Out form contains two input fields (for username and password) and a button.

The login function can be a component function or a redux action – we just need to add it using the connect function. This will be used for asynchronous validation.

import {login} from "../actions/actions";

/* If you need to connect your component to redux,
it's best to do it before applying reduxForm HOC*/
LoginPage = connect (mapStateToProps, {login})(LoginPage);

export default reduxForm({
    form: 'login'    
})(LoginPage);

Synchronous validation

In order to reduce back-end load and improve user experience, it’s best to perform synchronous validation wherever possible – for example we can check if username and/or password contain invalid characters.

The first step is to add the validation function via reduxForm HOC (higher-order component):

export default reduxForm({
    form: 'login',
    validate
})(LoginPage);

And add the validation function itself above the LoginPage container.

const validate = (values) => {
    const errors = {}
    if(!values.username) {
        errors.username = "Cannot be empty"
    }
    if(!values.password) {
        errors.password = "Cannot be empty"
    }
    return errors
}

Whenever we change the value of username or password, synchronous validation will run and check if both fields are populated. If a validation function returns errors, the form will not be submitted.

Asynchronous validation

If form values successfully pass synchronous validation, typically they are sent to a server via an asynchronous call. Using Redux Form, we can disable the form until our request is processed and display an error message if it wasn’t successful.

In the code shows earlier, handleSubmit will execute our login action:

export function login(values) {    
    return (dispatch) => {        
        return ApiConnector.login(values.username, values.password)
            .then(response => {
                if(response.data.success === true){
                  dispatch({
                    type: LOGIN
                  })
                } else {
                  throw new SubmissionError({        
                    _error: "Login failed!"
                    })

                }
            }) 
            .catch((error) => {              
              throw new SubmissionError({        
                _error: "Login failed!"
              })
            })
    }
}

Since this action is using an asynchronous call, we need to use Redux Thunk middleware. The ApiConnector.login function is a post request sent using axios. For asynchronous validation to work correctly, it’s important to return the Promise object.

We can see that if the request to the server fails (for example, if a server is down) or login is unsuccessful, an error message will be returned back to our form.

How can we display the error message? We will modify our form container to extracting the error from props and display it above the ‘Login’ button.

const {handleSubmit, error} = props
return (
  <form onSubmit={handleSubmit(login)}>
    <Field
      name='username'
      placeholder = "Enter username"
      component="input"
      type='text'
    />
    <Field
      name='password'
      placeholder = "Enter password"
      component="input"
      type='password'
    />
    {error && <strong>{error}</strong>}
    <button type="submit">Login</button>
  </form>
)

Initial values

In many case you want your form to have some initial values (possibly based on earlier user selection). This can be done by adding initialValues object to container props (for example through mapStateToProps).

function mapStateToProps(state) {
  return {
    initialValues: {
      username: "admin"
    }
  }
}

Typically initialValues will be populated from application state.

Changing values programmatically

There are situations, when one value is changed, you’d like to automatically change another value – for example, if someone wants to login using ‘guest’ username, the password is automatically changed to ‘guest’ as well.

First, onChange function must be added using reduxForm HOC.

export default reduxForm({
    form: 'login',
    validate,
    onChange
})(LoginPage);

And the onChange function can be added above the LoginPage component.

import {reduxForm, change} from "redux-form";

const onChange = (values, dispatch, props, previousValues) => {
  if(values.username === "guest") {
    dispatch( change ('login', 'password', 'guest');
  }
}

This can be very useful if you have one value conditionally dependent on another.

Summary

Using the information in this article, you should be able to implement Redux Form in your applications.

It is a very powerful tool, which pretty much includes all the functionality needed for handling forms. If you need more information, the best place to find it is:

www.redux-form.com

Origin: www.linkedin.com

All articles