Form validation is vital because it helps with the UX. It helps user know that there is something wrong with what they’ve entered or if they’ve missed something and so on.
A “robust” form validation helps with (at least):
- Client-side Validation: e.g. Check empty fields, password length etc.
- Instant Server-side Validation: e.g. Instantly check server if username is unique upon blur.
- OnSubmit Server-side Validation: e.g. Check server after submit
- Prevent Duplicate submission: e.g. Disable Submit-button after submit.
But implementing such a form validation is a lot of work in any framework including Redux apps. So how do you do form-validation in React Redux apps?
Use libraries 🎉😀! After doing some research the one I liked was: redux-form. I like it because it’s robust and it fits perfectly w/ React-Redux app development.
Update (Nov 30th 2016): The code had to be rewritten because redux-forum lib has undergone significant changes. So while the overall logic remains the same, the latest code in this blog doesn’t match anymore!
OK, Let’s see how to implement all those scenarios by taking using an example app.
Note: Click on the picture above to Zoom and read.
I’m will use the same multi-page blog app from “A Guide For Building A React Redux CRUD App”
Source code: https://github.com/rajaraodv/react-redux-blog
Live App: https://protected-escarpment-79486.herokuapp.com/posts/new
Twitter: https://twitter.com/rajaraodv (@rajaraodv)
Quick Refresher
If you haven’t read the A Guide For Building A React Redux CRUD App, here is a quick refresher.
- The app is a simple CRUD app that allows us to list, create and delete blog posts. We’ll add form validations to the PostsForm component that actually creates new Post.
2. PostsForm component is a “presentational” component. It’s job is to simply render the page and delegate event handling to it’s parent “PostsFormContainer” component.
3. PostsFormContainer has all JS logic and also deals with Redux. It also passes the result back to the PostsForm container.
Implementing redux-form
redux-form simply decorates(add more features) to the component. It comes w/ a reducer that changes the form’s state and it’s own container component generator similar to Redux’ own “connect” function. Below are the steps to implementing it to any regular form:
- Add it’s reducer to our list of reducers.
- Update Container Component: Replace “connect” function from redux w/ the one from redux-form. Note: redux-form after decorating our form, will internally call Redux’s “connect” function.
- Update Presentational Component: Redux-form passes additional props and callback functions to Presentational components that can be used to show error, delegate onSubmit and so on.
1. Implementing redux-form — Add it’s reducer
import { combineReducers } from ‘redux’;
import PostsReducer from ‘./reducer_posts’;
import { reducer as formReducer } from ‘redux-form’;//combineReducers adds all reducers into a single JSON of reducers
const rootReducer = combineReducers({
posts: PostsReducer, //← Posts
form: formReducer // ← redux-form
});export default rootReducer;
2: Implementing redux-form — Update Container Component
Click on the below picture to compare *before* & *after* PostsFormContainer
3. Implementing redux-form — Update Presentational Component
Click on the below picture to compare *before* & *after* PostsForm. I’ll go over each change in details in the following sections.
Scenario 1 : Add Client-side Validation
We need to update both PostsFormContainer and PostsForm components to add this validation.
Every scenario follow the same set of steps
STEP 1.1 Client-side Validation — Update PostsformContainer
In PostsFormContainer, simply add a function that’s called upon blur on any field. It receives a “values” object that contains current values of all the fields. It’s job is to check for any errors and return them like below:
//Client side validation
function validate(values) {
const errors = {};
if (!values.title || values.title.trim() === ‘’) {
errors.title = ‘Enter a Title’;
}
if (!values.categories || values.categories.trim() === ‘’) {
errors.categories = ‘Enter categories’;
}
if(!values.content || values.content.trim() === ‘’) {
errors.content = ‘Enter some content’;
}return errors;
}
Then, add the “validate” validation function and list of fields to track to the redux-form.
Note: To use ES6 abbreviation, the function name must be “validate”
export default reduxForm({
form: ‘PostsNewForm’, // ←A Unique name
fields: [‘title’, ‘categories’, ‘content’], //←Fields to track
validate //← Callback function for client-side validation
}, mapStateToProps, mapDispatchToProps)(PostsForm);
STEP 1.2 Client-side Validation — Update PostsForm
Receive new props “fields” and “handleSubmit” from redux-form.
const {fields: { title, categories, content }, handleSubmit} = this.props;
Instead of directly calling “createPost”, delegate it to “handleSubmit”
<form onSubmit={handleSubmit(this.props.createPost.bind(this))}>
The fields props(title, categories, content) that we received is actually an object that contains:
1. Several event listeners like “onBlur”, “onClick” etc and
2. field’s state like if it the field has any errors, has the field been clicked (touched) etc.
Update each field to use those props using “spread” operator( {…title}) like below. Also add any additional HTML to display error.
<input type=”text” className=”form-control” {…title} />
<div className=”help-block”>
{title.touched ? title.error : ‘’}
</div>
Scenario 2: Add Instant Server Validation
Since we’ll have to make server call, we need to add bunch of actions and reducers to let other components know what’s going on just in case they are interested (or for future purposes).
STEP 2.1 Create Actions, Action Creators and Reducers
I am using the Async Action Pattern described in “A Guide For Building A React Redux CRUD App”
In our app, we have the following actions: VALIDATE_POST_FIELDS, VALIDATE_POST_FIELDS_SUCCESS, VALIDATE_POST_FIELDS_FAILURE, RESET_POST_FIELDS.
We also have corresponding “Action Creators” and “reducers” for all 4 actions.
See source code here: Action Creators and Reducers.
STEP 2.2 Instant Server Check — Add Callback
Redux-form needs a function called asyncValidate to return Promise. And inside that we can do our regular “dispatch” actions to let other components know.
In addition, call “reject” or “resolve” to let redux-form know of the status.
//For instant async server validation
const asyncValidate = (values, dispatch) => { return new Promise((resolve, reject) => { dispatch(validatePostFields(values))
.then((response) => {
let data = response.payload.data;
let status = response.payload.status; //if there is an error
if(status != 200 || data.title || data.categories....) { //let other comps know of error by updating redux` state
dispatch(validatePostFieldsFailure(response.payload));
reject(data); //this is for redux-form itself } else { //let other comps know success by updating redux` state
dispatch(validatePostFieldsSuccess(response.payload));
resolve();//this is for redux-form itself
}
}); //dispatch
}); //promise};
STEP 2.2 Instant Server Check — Add to Redux-form config
Add the callback function(asyncValidate) and also add the field to track specifically for instant server check.
export default reduxForm({
...
asyncValidate, //←A Callback for instant server validation
asyncBlurFields: [‘title’], //← Fields to track for instant check
...
}, mapStateToProps, mapDispatchToProps)(PostsForm);
STEP 2.3 Instant Server Check — Update PostsForm
Redux-form sends another prop called: “asyncValidating”. This allows us to show “validating..” or some spinner. Everything else like fields, handleSubmit work the same as Scenario #1.
const {asyncValidating, fields: ..., handleSubmit} = this.props;<div className=”help-block”>
{asyncValidating === ‘title’? ‘validating..’: ‘’}
</div>
Scenario 3: OnSubmit Server-side Validation
Steps are similar to Scenario #2 (Add Instant Validation). Main difference is that you’ll use this in conjunction w/ actual Post submission. i.e. You’ll submit the form assuming everything is OK and then wait for server to tell if there was an error.
In this scenario, you’ll have to take care and distinguish b/w errors because of form’s data and errors due to server (500)
You’ll probably use this if you don’t want(or can’t have) instant server-checks but want to show server errors upon submit.
STEP 3.1 OnSubmit Validation — Update PostsformContainer
Add the function to deal w/ submission and errors. It should return a “Promise” and then implement “reject” and “resolve”.
//For any field errors upon submission (i.e. not instant check)//Note: In the below function, we kinda assume that the fields are valid and try to create post and handle errors if any later on.
const validateAndCreatePost = (values, dispatch) => {
return new Promise((resolve, reject) => { dispatch(createPost(values)).then((response) => {
let data = response.payload.data;
//error..
if(response.payload.status != 200) {
//let other components know by updating the redux` state
dispatch(createPostFailure(response.payload));
reject(data); //this is for redux-form itself
} else {
//let other components by updating the redux` state
dispatch(createPostSuccess(response.payload));
resolve();//this is for redux-form itself
}
});//dispatch });//return};
Add the function to “mapDispatchToProps” as this is function should now be called by the PostsForm component when Submit button is clicked.
const mapDispatchToProps = (dispatch) => {
return {
createPost: validateAndCreatePost,
resetMe: () =>{
dispatch(resetNewPost());
}
}
}
Scenario 4: Prevent Duplicate submission
This is pretty simple to implement. Redux-form passes a boolean “submitting” to the PostsForm component. All we need to do is to update our “Submit” button to enable or disable itself based on that.
4.1. Update PostsForm
Receive “submitting” boolean prop. This is updated appropriately and automatically by redux-form depending on if we are submitting or not.
const {asyncValidating, fields: { title, categories, content }, handleSubmit, submitting } = this.props;
Update the Submit button like below:
<button type=”submit” className=”btn btn-primary” disabled={submitting} >Submit</button>
Source code: https://github.com/rajaraodv/react-redux-blog
Live App: https://protected-escarpment-79486.herokuapp.com/posts/new
Twitter: https://twitter.com/rajaraodv (@rajaraodv)
That’s it for now!🙏
🎉🎉🎉 If you like this post, please ❤❤❤ it below and please share it on Twitter (https://twitter.com/rajaraodv)🎉🎉🎉
My Other Blogs
ES6
WebPack
- Webpack — The Confusing Parts
- Webpack & Hot Module Replacement [HMR]
- Webpack’s HMR And React-Hot-Loader — The Missing Manual
Draft.js
React And Redux :
- Step by Step Guide To Building React Redux Apps
- A Guide For Building A React Redux CRUD App (3-page app)
- Using Middlewares In React Redux Apps
- Adding A Robust Form Validation To React Redux Apps
- Securing React Redux Apps With JWT Tokens
- Handling Transactional Emails In React Redux Apps
- The Anatomy Of A React Redux App
- Why Redux Need Reducers To Be “Pure Functions”