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.
I’ll also be using Bit to share and reuse React components. You can share your components into a catalog (no refactoring), view a live playground, and install them with NPM right from the catalog(!). You don’t have to use it, but might want to give it a try to avoid writing components twice.
App Setup
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')
);
Build a Form using Redux-Form
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.
Passing Custom Reusable Components to Field
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.
Form Initial Values
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.
Client-Side Validation
There are 2 way that we can use to manage validation in redux-form
. They are:
Using Validation Function
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.
- 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”. - 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.
Using Field-Level Validation
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]}
Accessing other Field Values
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:
Submit Validation
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.
In the End…
A developer may think that creating forms is easy, and in some cases that is the case. But in case of complex forms that contain a lot of state, having the right set of tools makes all the difference.
Personally, I think that Redux-Form can create really great forms in React, better than the ones that React can create on its own. Redux-Form also makes it easy to handle a form’s state. The way that Redux-Form interacts with React and Redux is truly amazing.
I hope this post help you understand how Redux-Form works, and that you will give it shot the next time you are creating a form component for your app.
You can take a look at the entire code of this Form Component here: