Build Awesome Forms In React Using Redux-Form

May 28, 2018 0 Comments

Build Awesome Forms In React Using Redux-Form

 

 

Redux-Form is yet another awesome Redux library that gives us a new way to manage the state of our forms in the React App.

Redux-Form not only integrates the state of our form with the other state that is managed by Redux, but it also lets us track the state of the form with precise accuracy using Redux Dev Tools.

In this post, I’ll show you how to link redux-form with a React App, and also go over things like validation and creating reusable form components.

Let’s create a new React app using create-react-app. I will name my app as form.

$ create-react-app form
$ cd form

Now, let’s install a couple of dependencies to this project. I need redux for state management, react-redux to link Redux with React, and redux-form to handle the forms that I am going to create in this app.

yarn add redux react-redux redux-form

Next, open the app directory in a code editor (I love VS Code) and delete all the files inside src folder except the index.js file. We are going to write our own files.

Create a new folder inside src and name it as App. Inside create a new file called index.js with the following code:

Go to src/index.js and remove the reference for the files we just deleted. Then, import the Provider component from react-redux and createStore and combineReducers functions from redux. I also need to import the reducer from redux-form. But, to avoid any conflicts, I’ll do it by renaming it as formReducer.

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

I now need to specify that formReducer is one of the app’s reducer functions.

const reducers = {form: formReducer};

In a real world app, you will have more than just one reducer. You will have to specify them all here inside the reducers object. After specifying the reducers, we need to combine them all into a single reducer using the combineReducers function like this:

const reducer = combineReducers(reducers);

Finally, we will set up our redux store using the createStore function. I will pass in the reducer as an argument to this function.

let store = createStore(reducer);

Even if you have a single reducer in you entire app, you will still have to do these things.

Only one more thing left to do here. Wrap the App component with the Provider component inside the ReactDOM‘s render function. Provider helps us pass the store into our App component.

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

Time to build us a nice form.

Create a new folder inside src called components. This folder structure is extremely helpful in breaking down my app into blocks of code that can later be re-used in other projects as well.

Inside the components folder, create another folder called LoginForm. This folder will contain a index.js file with this code in it.

First, I import React and Component from the React package. Since I am going to create a form in this file, I need a few things from the redux-form package called Field and reduxForm.

Next, create a new class component called LoginForm. Here I’m using the Field to create the form fields Username and Password. I also have a Submit button inside this form.

The Field component has a couple of props inside it. The name prop acts as an identifies for the field and the component prop refers to the html element. Here, I need the Field to act as an input element, so I will set the component prop’s value as input.

I also need to hook up an onSubmit method to the form. I will pass in another method called handleSubmit, which is going to come from our props.

reduxForm is used to decorate the LoginForm component. I need to pass a name for this for here, which is what I have used login here for. All that’s left to do is export it. All that is left to do is import it into src/App/index.js and call it inside the App component’s render method.

We also pass it an onSubmit method called this.submit. The submit method here is going to just alert me the values that the user enters in the form.

Run the start script in the terminal using NPM/Yarn, enter some values in the field, and you will get this as the output.

Take a look at the Field component for Username. Although we are passing a string into the component prop, we can achieve better control over our form if we pass a component instead.

Let’s create a new folder inside components called Field. Create a file named index.js and write the following code inside it.

After import React, I have simply created my own input field that can take props.

Go back to the LoginForm/index.js file and pass in the myInput component as a prop in both of the Field components.

<Field
name="username"
component={myInput}
type="text"
placeholder="Username"
/>
<Field
name="password"
component={myInput}
type="password"
placeholder="Password"
/>

If you take a look at the app in the browser, you will see that nothing has really changed.

If we want to set the initial default values to our form fields, redux-form allows us to that as well! To set an initial value, we need to pass a prop called initialValues into the form component.

Go to the src/App/index.js file and pass the initialValues prop into the LoginForm component. This prop needs to call a another function where we are actually storing the initial values.

<LoginForm 
onSubmit={this.submit}
initialValues={this.getInitialValues()}
/>

We also need to define the getInitialValues(). getInitialValues will return an object whose keys correspond to the names of the form’s fields.

getInitialValues () {
return {
username: 'rajat',
password: '',
};
}

Reload the app page and you will see that the username field already has a value inside it.

There are 2 way that we can use to manage validation in redux-form. They are:

Lets create a new folder inside src called Validation. This folder will contain an index.js file with the following code.

I have a created a function called validate that takes in inputs from the form and initially has an empty errors object.

Next, I have listed out a couple of error scenarios, and what I want redux-form to do when such a scenario occurs.

  1. If the user does not enter anything in the username field, the app should show a message saying “Enter you Username”. If the user does enter a username that is something other than “rajat”, then the app should show a message saying “Username is incorrect”.
  2. If the user does not enter anything in the password field, the app should a message saying “Enter you Password”.

Next, go to LoginForm/index.js and below the input field, add the following code:

{
meta.error && meta.touched &&
<div>
{meta.error}
</div>
}

What this code does is that it first checks where there is any data stored inside the error object. If that is the case, it will then check if the user has interacted with the input field. If this is also true then the app will return the data stored inside the error object.

Finally, we need to go to the index.js file inside the LoginForm folder, import the validate function, and call it inside the reduxForm decorator.

import {validate} from '../../Validation';
...
LoginForm = reduxForm ({
form: 'login',
validate,
}) (LoginForm);

Reload the app page in the browser and you will now be able to see the errors upon interaction.

In Field-level validation, we pass the validation functions directly into the field. This way, instead of having one giant function, we will have instead have several smaller functions that deal with specific validation related scenarios.

Erase everything inside the Validation/index.js function and write the following code snippet instead.

First, I have created a validation function called requiredInput that checks if the user has entered any input in the field. If not, then this function will return a message saying that the “Input is Require”.

Second, I have created another validation function called correctInput. This function checks if the input given by the user is what the app was expecting to receive and if it isn’t then it will return a message saying the input is incorrect.

We now need to hook this functions to our form. Go to LoginForm/index.js and replace the previous import statement for validate function with this:

import {requiredInput, correctInput} from '../../Validation';

Also, remove the reference to validate from the reduxForm decorator. Next, add a new attribute to the username field called validate and pass requiredInput and correctInput.

validate={[requiredInput, correctInput]}

Do the same thing for the password field, but only pass requiredInput.

validate={[requiredInput]}

Before going further, let’s create a new Field component inside the LoginForm/index.js file.

<Field
name=”confirm-password”
component={myInput}
type=”password”
placeholder=”Confirm Password”
validate={[requiredInput]}
/>

Usually when we have a Password and Confirm Password fields, we need to check if the input of Password matches with the input of Confirm Password.

To do so, I am going to create a new validation function inside the Validation/index.js file called matchInput.

export const matchInput = (input, allInputs) =>
input === allInputs.password ? undefined : 'Passwords do not match';

This function will check the input of a field and check if it is equal to the input of the Password field.

All that is left to do is import this new validation function inside the LoginForm/index.js file and pass it as a prop to the Confirm Password field.

<Field
name="confirm-password"
component={myInput}
type="password"
placeholder="Confirm Password"
validate={[requiredInput, matchInput]}
/>

Reloading your app should get you this:

A real-world form should also be able to check if the username that we are submitting is available or not. Redux-Form has provision to make this check when the user clicks on the submit button.

In the src/App/index.js file, import the SubmissionError function from redux-form.

import {SubmissionError} from 'redux-form';

Next, restructure the submit method as shown below:

submit = inputs => {
if (['rajat', 'batman'].includes (inputs.username)) {
throw new SubmissionError ({
username: 'Username already taken',
});
} else {
window.alert (JSON.stringify (inputs));
}
};

Here, the submit method checks if the username input given by the username already exists in the given array. If that is the case, redux-form will throw an error. Else, it will work as it previously did.


Tag cloud