— Tutorial, Frontend, JavaScript — 10 min read
There are few things less appealing than forms but they’re still one of the most direct ways for a user to dump a hunk of data into your server. To make the process of filling a form as painless as possible, one needs a good form system that handles common concerns like:
Handling field and form data Validation Submission Integration with the rest of our application
There are many form systems in React. The most common choices seem to be either going with vanilla React components and state or using one of the many libraries that expose Higher Order Components (HOCS) that may or may not integrate with Redux.
‘Redux Form’ is one of those libraries, one that manages your form state in Redux. I haven’t tried all other options so I’m not claiming this is the one true form tool, but I’ve tried vanilla React forms and custom in-house React form systems, and ‘Redux Form’ has provided the nicest form-making experience of them all.
We’re going to make a form.
In loose order, we’ll be covering these topics using Redux Form v7:
1// package.json23{4 "name": "redux-form-v7-example",5 "version": "0.1.0",6 "private": true,7 "dependencies": {8 "moment": "^2.18.1",9 "react": "^15.6.1",10 "react-addons-shallow-compare": "^15.6.0",11 "react-dates": "^12.6.0",12 "react-dom": "^15.6.1",13 "react-redux": "^5.0.6",14 "react-scripts": "1.0.13",15 "redux": "^3.7.2",16 "redux-form": "^7.0.4",17 "reselect": "^3.0.1",18 "tachyons": "^4.8.1"19 },20 "scripts": {21 "start": "react-scripts start",22 "build": "react-scripts build",23 "test": "react-scripts test --env=jsdom",24 "eject": "react-scripts eject"25 }26}
Use create-react-app to create a new react application and then structure the folders and files to follow this shape:
Our entry point will be index.js. This is where we wrap our main component, FormContainer, with our Redux store, and render our app onto the page using ReactDOM.
1// Index.js23import React from 'react';4import ReactDOM from 'react-dom';5import { Provider } from 'react-redux';67import FormContainer from './modules/form/form.container';8import configureStore from './store';910const store = configureStore();1112ReactDOM.render(13 <Provider store={store}>14 <FormContainer />15 </Provider>,16 document.getElementById('root')17);
The store.js file exports a function that configures a store. It’s set up in such a way that we can easily plug in middleware and enhancers like Redux Devtools.
1// store.js23import { createStore, applyMiddleware, compose } from 'redux';4import createReducer from './reducers';56const composeEnhancers =7 typeof window === 'object' &&8 (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__9 ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__10 : compose);1112export function configureStore() {13 const middlewares = [];14 const store = createStore(15 createReducer(),16 {},17 composeEnhancers(applyMiddleware(...middlewares))18 );19 return store;20}2122export default configureStore;
And the corresponding reducer.js file exports a function that combines all reducers into one. I like to keep my root reducer file separate from my store to keep things more modular.
1// reducers.js23import { combineReducers } from 'redux';4import { reducer as formReducer } from 'redux-form';56export default function createReducer(extraReducerObjects = {}) {7 return combineReducers({8 form: formReducer,9 ...extraReducerObjects10 });11}
For the form.container.js and form.component.js files let’s just make filler components for now, like this:
1src/modules/form/form.container.js23import React, { Component } from 'react';45class FormContainer extends Component {6 render() {7 return (8 <div>9 a what whAT10 </div>11 );12 }13}1415export default FormContainer;
It’s common practice in React applications to separate concerns of data and presentation into containers and components. We’re going to put all of the stuff that deals with data and state in the container, and all of the stuff that deals with how the form looks into the component. This will serve as a loose guide as to how we structure our components.
Separating our components in this way not only makes it easier to test but also easier to reason. If you want to change how something looks you change the component, and If you want to change how something works you change the container.
Let’s start making our container, a normal React component that renders FormComponent and wraps it with Redux Form’s reduxForm() helper. reduxForm() is a function that takes in a form configuration object and returns a HOC, a function that takes and returns a component.
The purpose of such wrapping in this case is to return the wrapped component with a bunch of helpers -- functions that can change the state of the form or give you information as to whether a form field was touched or validated, or which fields are registered -- passed through as props.
1// src/modules/form/form.container.js23import React from 'react';4import { reduxForm } from 'redux-form';56import FormComponent from './form.component';78export const FormContainer = props => {9 return (10 <FormComponent />11 );12}1314const formConfiguration = {15 form: 'my-very-own-form'16}1718export default reduxForm(formConfiguration)(FormContainer);
If you place a console.log(props); right before the return in the container you can actually see these props for yourself:
All of the above helpers act sort of like a master control for the form you wish to wrap by providing action creators to change the state of the form. Here’s a comprehensive list from the documentation:
https://redux-form.com/7.0.4/docs/api/props.md/
Let’s use one of these props, handleSubmit(), a function that runs validation and invokes a function we define and pass in with all of the form values passed to it as a parameter.
We’ll pull out handleSubmit from the passed props with ES6 destructuring and define our own submit handler to be used with handleSubmit, and then pass the two props down to our form component:
1// src/modules/form/form.container.js:23import React from 'react';4import { reduxForm } from 'redux-form';56import FormComponent from './form.component';78export const FormContainer = ({ handleSubmit }) => {9 const submitForm = (formValues) => {10 console.log('submitting Form: ', formValues);11 }1213 return (14 <FormComponent15 onSubmit={submitForm}16 handleSubmit={handleSubmit}17 />18 );19}2021const formConfiguration = {22 form: 'my-very-own-form'23}2425export default reduxForm(formConfiguration)(FormContainer);
Besides the reduxForm HOC provided by Redux Form, another big helper is the Field component. It’s a normal component that represents a field in a form, but what makes it really unique is that each Field is individually connected to Redux state.
If you’ve ever created forms with React then you should be familiar with the concept of controlled components.
https://facebook.github.io/react/docs/forms.html
HTML5 form tags like ‘input’ and ‘textarea’ often maintain their own state that gets updated upon user input. This leads to unnecessary complications when also handling React and Redux state because now data state has to be synced on three different levels, with no huge benefit. It’s much easier to have the user input update a single source of truth (Redux) and have the inputs draw their state and value from that source. It makes for a more unidirectional flow that’s easier to follow and debug.
We can use Field as is like this (a name prop is required to set the key in the reducer):
1// src/modules/form/form.component.js23import React from 'react';4import { Field } from 'redux-form';56export const FormComponent = ({ handleSubmit, onSubmit }) => {7 return (8 <div>9 <h1>My Very own Form</h1>10 <form onSubmit={handleSubmit(onSubmit)}>11 <Field12 name="firstName"13 component="input"14 />15 <button type="submit">Submit</button>16 </form>17 </div>18 );19}2021export default FormComponent;
Another neat feature of Field is that we can pass our own component into it via the component prop because Field is essentially a component that acts much like a HOC in that in can accept and return a new component. And similar to how reduxForm wrapped our container and passed in a lot of utility functions via props, Field does the same and passes a lot of utility helpers onto the component we pass in via the component prop.
By default we can pass in strings like ‘input’ that will render an input tag for us, but if we want more customization as to how our field will look or how it manages data and handles events, we need to pass in our own custom component.
Here’s how our form looks right now:
And look at all the cool data we got in Redux State! Each form and field is connected to the Redux Form reducer and provide information we can use in our own components.
(a Redux DevTools chart of our Redux store split into its reducers)
And when we click the submit button, it’ll invoke our submitForm method with the form data as we discussed:
Let’s create a text.js file in our new folder, components, which will return a component that receives props from Redux Form’s Field component and renders an input tag.
1// src/modules/components/text.js23import React from 'react';45export const Text = ({ label, input }) => {6 console.log('inputStuff: ', input);7 return (8 <div>9 <div>10 {label}11 </div>12 <div>13 <input {...input} placeholder={label} type="text" />14 </div>15 </div>16 );17}1819export default Text;
Notice how we spread props.input into our input field. The props passed by Field include a bunch of event handlers to handle clicks, changes, blurs, and other standard form field events, by accepting an event object and dispatching an action to change the state of the form in the form’s reducer.
Of course, each of these methods can be further customized if you want to implement your own onClick, onChange, or whichever, maybe to hit an API endpoint before dispatching an action to Redux or filtering through some data and performing other side effects. For now, we’ll keep it simple and stick with the default ones provided.
To use our new component, we just import it and feed it as a component prop on our Field:
1// form/form.component.js:23import React from 'react';4import { Field } from 'redux-form';5import Text from '../components/text';67export const FormComponent = ({ handleSubmit, onSubmit }) => {8 return (9 <div>10 <h1>My Very own Form</h1>11 <form onSubmit={handleSubmit(onSubmit)}>12 <Field13 name="firstName"14 label="First Name"15 component={Text}16 />17 <button type="submit">Submit</button>18 </form>19 </div>20 );21}2223export default FormComponent;
As a quick aside, our forms look ugly right now. Yes, there is normal css, sass, postcss, styled-components, and a plethora of other CSS tools we can use, but we’ll be using Tachyons for now. It’s simple, expressive, responsive, and composable. I like to think of it like bootstrap but much lighter, but It’s easier to show than explain.
First we need to import tachyons into our root component:
1// Index.js23import React from 'react';4import ReactDOM from 'react-dom';5import { Provider } from 'react-redux';6import "tachyons"78import FormContainer from './modules/form/form.container';9import configureStore from './store';1011const store = configureStore();1213ReactDOM.render(14 <Provider store={store}>15 <FormContainer />16 </Provider>,17 document.getElementById('root')18);
Then we start styling. ‘m’ stands for margin, ‘w’ for width, ‘p’ for padding, ‘v’ for vertical (top and bottom), ‘a’ for all (top, bottom, left, right), and the numbers are general sizes. Tachyons class names were made to be short but to resemble native CSS properties as close as possible.
1// src/modules/component/text.js23import React from 'react';45export const Text = ({ label, input }) => {6 return (7 <div className="mv4 w-100">8 <div className="b sans-serif pv2 w-100">9 {label}10 </div>11 <input12 {...input}13 placeholder={label}14 type="text"15 className="pa2 ba b--black-40 w-100"16 />17 </div>18 );19}2021export default Text;
1// src/modules/form/form.component.js23import React from 'react';4import { Field } from 'redux-form';5import Text from '../components/text';67export const FormComponent = ({ handleSubmit, onSubmit }) => {8 return (9 <div className="flex flex-column justify-center items-center">10 <h1>My Very own Form</h1>11 <form12 className="w-80"13 onSubmit={handleSubmit(onSubmit)}14 >15 <Field16 name="firstName"17 label="First Named"18 component={Text}19 />20 <button21 type="submit"22 className="link br2 bg-blue white dim pa3 f6 sans-serif b--blue ba"23 >24 Submit25 </button>26 </form>27 </div>28 );29}3031export default FormComponent;
And now our form looks slightly better:
Let’s just add a few more fields so that we have more to play with. We can reuse the text component we just made like this:
1// form/form.component.js:23import React from 'react';4import { Field } from 'redux-form';5import Text from '../components/text';67export const FormComponent = ({ handleSubmit, onSubmit }) => {8 return (9 <div className="flex flex-column justify-center items-center">10 <h1>My Very own Form</h1>11 <form12 className="w-80"13 onSubmit={handleSubmit(onSubmit)}14 >15 <Field16 name="firstName"17 label="First Named"18 component={Text}19 />20 <Field21 name="lastName"22 label="Last Name"23 component={Text}24 />25 <Field26 name="email"27 label="Email"28 component={Text}29 />30 <button31 type="submit"32 className="link br2 bg-blue white dim pa3 f6 sans-serif b--blue ba"33 >34 Submit35 </button>36 </form>37 </div>38 );39}4041export default FormComponent;
And they’ll all be tracked in Redux State:
Sometimes it just makes sense to select an option from a dropdown rather than manually typing in an answer. We use the default HTML5 select tag along with option to structure our dropdown, but we still keep the state of the field in our reducer by passing in the props passed by Field. Here’s how you would make a basic, reusable select component:
1// src/modules/components/select.js23import React from 'react';45export const Select = props => {6 const renderSelectOptions = (key, index) => {7 return (8 <option9 key={`${index}-${key}`}10 value={key}11 >12 {props.options[key]}13 </option>14 );15 }1617 if (props && props.options) {18 return (19 <div className="mv3 w-100">20 <div className="b sans-serif pv2 w-100">{props.label}</div>21 <select {...props.input} className="pa2 input-reset ba b--black-40 w-100">22 <option value="">Select</option>23 {Object.keys(props.options).map(renderSelectOptions)}24 </select>25 </div>26 )27 }28 return <div></div>29}3031export default Select;
Now we can add another Field to our form that takes in our select component and a bunch of options we pass through as props:
1import React from 'react';2import { Field } from 'redux-form';34import Text from '../components/text';5import Select from '../components/select';67export const FormComponent = ({ handleSubmit, onSubmit }) => {8 return (9 <div className="flex flex-column justify-center items-center">10 <h1>My Very own Form</h1>11 <form12 className="w-80"13 onSubmit={handleSubmit(onSubmit)}14 >15 ...16 <Field17 name="meatChoice"18 label="Meat Choice"19 component={Select}20 options={{21 pork: 'Pork',22 beef: 'Beef',23 chicken: 'Chicken'24 }}25 />2627 ...2829export default FormComponent;
HTML5 radio inputs are a bit weird. There’s no explicit ‘radio’ tag like there is for ‘select’, so it’s the same as our ‘input’ tag only with a specified type of ‘radio’.
This is how it looks like in normal HTML5:
1<form>2 <input type="radio" name="gender" value="male" checked> Male<br>3 <input type="radio" name="gender" value="female"> Female<br>4 <input type="radio" name="gender" value="other"> Other5</form>
So how do we convert that to React/Redux Form? Maybe we could use three separate Field components and make them all of type radio, but that leads to a lot of boilerplate just to render one field. Ideally, we want something like our ‘text’ input or ‘select’ input where we feed a single component into the component prop of Field.
We’ll use the same pattern as we did in our select component, where we passed in an ‘options’ prop that we mapped over to render individual snippets of JSX.
Keep in mind that for radio inputs, each input option has to have the same name prop while still maintaining a unique key to satisfy React:
1// src/modules/components/radio.js23import * as React from 'react';4import { Field } from 'redux-form';56export const Radio = props => {7 if (props && props.input && props.options) {8 const renderRadioButtons = (key, index) => {9 return (10 <label className="sans-serif w-100" key={`${index}`} htmlFor={`${props.input.name}-${index}`}>11 <Field12 id={`${props.input.name}`}13 component="input"14 name={props.input.name}15 type="radio"16 value={key}17 className="mh2"18 />19 {props.options[key]}20 </label>21 )22 };23 return (24 <div className="mv3 w-100">25 <div className="b sans-serif pv2 w-100">26 {props.label}27 </div>28 <div>29 {props.options &&30 Object.keys(props.options).map(renderRadioButtons)}31 </div>32 </div>33 );34 }35 return <div></div>36}3738export default Radio;
And now we can plop it into our form just like this:
1// src/modules/form/form.component.js:23<Field4 name="spiceLevel"5 label="Spice Level"6 component={Radio}7 options={{8 mild: 'Mild',9 medium: 'Medium',10 hot: 'hot'11 }}12/>
Little boxes that can be checked or unchecked. It’s worth mentioning that even though a checkbox appears to have two states, checked and unchecked, behind the scenes, it actually has three states, checked (true), unchecked (false), and unchecked (undefined). Keep this in mind as it may bite you later on.
1// src/modules/components/checkbox:23import React from 'react';45export const Checkbox = props => {6 return (7 <div className="flex items-center mv4 w-100">8 <input9 {...props.input}10 className="mr2"11 type="checkbox"12 checked={props.input.value}13 />14 <div className="sans-serif">{props.label}</div>15 </div>16 );17}1819export default Checkbox;
1// src/modules/form/form.component.js23...4<Field5 name="wantsFries"6 label="Would you like fries with that?"7 component={Checkbox}8/>9...
There are plenty of libraries offering datepickers, many of which expose a single component we can drop in. The tricky part is that these datepicker components often have their own React state and spreading our Field props into these components often ends up not working.
It’s okay for the datepicker to have its own internal state to keep track of things like whether to display the UI or not, but we still want to keep the value of the field tracked in Redux state, like the rest of our fields.
Redux Form comes with many built in action creators, one of which can explicitly change the value of a Field. We pass that action creator, change, down through our component as props and use it in combination with a custom handleDateChange event handler to dispatch an action to change the value of the field in Redux every time the date changes.
1// src/modules/components/datepicker.js23import React from 'react';4import moment from 'moment';5import { SingleDatePicker } from 'react-dates';6import 'react-dates/lib/css/_datepicker.css';78export class Datepicker extends React.Component {9 constructor(props) {10 super(props);11 this.state = {12 date: moment(),13 focused: false14 };15 }1617 changeActiveDateWidget = () => {18 this.setState({19 activeDateWidget: !this.state.activeDateWidget,20 });21 }2223 handleDateChange = (date) => {24 this.setState({ date });25 this.props.change(this.props.input.name, date)26 }2728 render() {29 return (30 <div className="mv4 w-100">31 <div className="b sans-serif pv2 w-100">{this.props.label}</div>32 <SingleDatePicker33 date={this.state.date} // momentPropTypes.momentObj or null34 onDateChange={this.handleDateChange} // PropTypes.func.isRequired35 focused={this.state.focused} // PropTypes.bool36 onFocusChange={({ focused }) => this.setState({ focused })} // PropTypes.func.isRequired37 showClearDate={true}38 numberOfMonths={1}39 />40 </div>41 );42 }43}4445export default Datepicker;
1// src/modules/form/form.component.js23...4import Datepicker from '../components/datepicker';56export const FormComponent = ({ handleSubmit, onSubmit, formValues, change }) => {7 return (8 <div className="flex flex-column justify-center items-center">9 <h1>My Very own Form</h1>10 <form11 className="w-80"12 onSubmit={handleSubmit(onSubmit)}13 >14 ...15 <Field16 name="orderDate"17 label="Order Date"18 component={Datepicker}19 change={change}20 />21...2223export default FormComponent;
Let’s say you needed to create a dynamic form in which you wanted to show a field, but only if the user filled out a previous field prior with a certain value. Maybe we only want to show a checkbox if the user has selected ‘hot’ as a value in our radio field.
Using the getFormValues selector, we can select form values and use some vanilla JavaScript ternary logic to express our form output:
1// src/modules/form/form.container.js:23import React from 'react';4import { connect } from 'react-redux';5import { reduxForm, getFormValues } from 'redux-form';67import FormComponent from './form.component';89export const FormContainer = props => {10 const submitForm = (formValues) => {11 console.log('submitting Form: ', formValues);12 }1314 return (15 <FormComponent16 formValues={props.formValues}17 change={props.change}18 onSubmit={submitForm}19 handleSubmit={props.handleSubmit}20 />21 );22}2324const mapStateToProps = state => ({25 formValues: getFormValues('my-very-own-form')(state),26});27const formConfiguration = {28 form: 'my-very-own-form',29}3031export default connect(mapStateToProps)(32 reduxForm(formConfiguration)(FormContainer)33);
1// src/modules/form/form.component23import React from 'react';4import { Field } from 'redux-form';56import Text from '../components/text';7import Select from '../components/select';8import Radio from '../components/radio';9import Checkbox from '../components/checkbox';1011export const FormComponent = ({ handleSubmit, onSubmit, formValues }) => {12 return (13 <div className="flex flex-column justify-center items-center">14 <h1>My Very own Form</h1>15 <form16 className="w-80"17 onSubmit={handleSubmit(onSubmit)}18 >19 ...20 <Field21 name="spiceLevel"22 label="Spice Level"23 component={Radio}24 options={{25 mild: 'Mild',26 medium: 'Medium',27 hot: 'hot'28 }}29 />30 {formValues && formValues.spiceLevel === 'hot' ? (31 <Field32 name="wantsFries"33 label="Would you like fries with that?"34 component={Checkbox}35 />36 ) : ''}3738 <button39 type="submit"40 className="link br2 bg-blue white dim pa3 f6 sans-serif b--blue ba"41 >42 Submit43 </button>44 </form>45 </div>46 );47}4849export default FormComponent;
And now, the ‘wantsFries’ field will only show if the user selects ‘hot’ for the ‘spiceLevel’ field:
A little thing to note with these fields is that the values will remain in Redux state even if they are not displayed. If you checked ‘wantsFries’, and then clicked ‘medium’ on ‘spiceLevel’, the ‘wantsFries’ field would disappear but the value would still stick in state.
This can be fixed with some plain old React/Redux/JS patterns by manually setting the value in the reducer as we want at certain lifecycle methods or other events, as we did in the datepicker.
I’ve always liked forms that validate on every key press rather than having to fill out everything, hit the submit button, wait for the response, and then go hunting for all the possible errors. You can do both in Redux Form, but this section will focus on real-time client side validation.
Redux Form gives you the option of passing in all of the validation functions into the Redux Form configuration object or passing validation functions to individual Field components. I prefer the second method more because you get finer control of your validation process, and I don’t like doing much with configuration.
To create a validator, make a function that takes in ‘value’ (passes the value of the field on every update), and returns an error depending on the logic you write. Validators also get passed values of other fields and props too, in case you ever need to write validation logic requiring more data on the form and fields. Let’s make a simple required validator that returns an error if there is no value:
1// src/modules/form/form.validators.js23// validate : (value, allValues, props) => error [optional] #45export const required = (value) => {6 if (!value) {7 return 'This field is required!'8 }9};
And then you feed it into your Field component as another prop:
1// src/modules/form/form.component.js23import { required } from './form.validators'4…5<Field6 name="firstName"7 label="First Named"8 component={Text}9 validate={required}10/>11...
You could also pass in an array of validators if you wanted more than one.
Inside the component, you’re going to want to create some logic as to what to do or display when an error on the field pops up:
1// src/modules/components/text.js23import React from 'react';45export const Text = props => {67 return (8 <div className="mv4 w-100">9 <div className="b sans-serif pv2 w-100">10 {props.label}11 </div>12 <input13 {...props.input}14 placeholder={props.label}15 type="text"16 className="pa2 ba b--black-40 w-100"17 />18 {props.meta && props.meta.error && props.meta.touched && (19 <div className="sans-serif red">20 {props.meta.error}21 </div>22 )}23 </div>24 );25}2627export default Text;
You can do a lot of things upon submit because it’s all just plain actions and reducers. All the patterns you’re used to in React and Redux can be applied here. You can integrate form submission with redux-sagas, redux-observables, thunks and promises, or whatever you desire. In our example, we used the built in handleSubmit helper that takes an onSubmit prop, which we defined ourselves.
What handleSubmit does is it runs validation on the form and if valid it calls onSubmit with the contents of the form data. What onSubmit does is completely up to you.
There may be instances where you need more flexibility in form submission, and in such cases I recommend throwing out handleSubmit and using the built-in action creators and selectors to dispatch actions to start form submission, run validation, and perform asynchronous side effects, all of which can be defined by yourself.
We made form widgets, learned how to use some built-in action creators and selectors, and made a form with Redux Form version 7.
I hope you can see how robust Redux Form can be and how nice it plays with standard React and Redux patterns. Next time you need to integrate a bunch of forms, give Redux Form a shot.