React Javascript
Get Started with redux-form
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.
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.