Working with Redux-Form
I refactored Terrapin’s frontend web app Althea last week to store more information in our Redux store. A big portion of that refactor involved converting all of our forms to use Redux-Form.
I wanted to write about how that process went and give a guide that I can look to in the future for doing similar refactoring. A lot of this content is taken from Erik Rasmussen’s (the creator of Redux-Store) talk about the library.
Introduction
Forms are one of the hardest things to take care of as a frontend developer. It’s hard because React stores state locally and forms usually require you to submit higher up the component tree. This can gets messy very fast.
Not only this, but frontend developers should strive to make user interaction with their application delightful. This task can be daunting when it comes to forms because users don’t want to spend time filling out forms, they want to use your app! Filling out forms is where a lot of users will drop off from using your application, especially if it generates an error when they submit it, so it’s crucial to make this process as painless as possible by providing good feedback to users.
Luckily, React-Form solves both of these issues by (1) maintaining the global state of the form and enforcing unidirectional data flow by dispatching actions that filter through reducer functions to mutate state and get the next state and (2) providing a wrapper that injects props, callbacks, and form state to help decorate your vanilla HTML input
elements.
Setting it up
After npm installing Redux-Form, setting it up within your application is pretty straight forward. Start by inserting a new reducer called form
:
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';export const makeRootReducer = (asyncReducers) => {
return combineReducers({
form: formReducer,
...asyncReducers,
});
};export default makeRootReducer;
Then wrap your form component in Redux-Form’s Higher Order Component:
import React from 'react'
import { reduxForm } from 'redux-form'let ExampleForm = ({ handleSubmit }) =>
<form onSubmit={handleSubmit((values) => someAction(values)} >
<input type='text' name='fName' />
<input type='text' name='lName' />
<input type='text' name='email' />
<button type='submit'>Submit</button>
</form>ExampleForm = reduxForm({
form: 'exampleForm'
})(ExampleForm)export default ExampleForm
When you wrap your form in the Redux-Form HOC, it creates an entry into Redux state for your form and maintains focus, change, blur events, and values for registered form fields. It also injects a handleSubmit
prop into the component that accepts a function to be called when the form is submitted and passes your form’s values
as an argument.
What’s sweet is that this allows me to access the contents of any of my forms from any component connected to Redux via state.form.formName
. So for the example above, I can access that form’s information via state.form.exampleForm
. This can come in really handy if you have dynamic fields within your form.
In our Terrapin application, we have a form that allows users to select what method they want to be paid out in (Paypal or Venmo), and so we can update the payoutValue
field to display ‘Type PayPal Username’ or ‘Type Venmo Username’ depending on the user’s selection in the previous field.
Pretty, pretty, pretty, pretty good.
The Field
Component
A lot of the value from Redux-Form comes from the Field
component that it provides. Field
requires two props: a name
string to identify the field in your redux store and a component
component which is the React component that contains your HTML input
element.
The component passed into component
injected with two props: an input
prop that contains standard information related to the HTML input element (value
, checked
, name
, onChange
, etc) and a meta
props which contains more in-depth information related to the field (error
, touched
, valid
, active
, autofilled
, etc).
import React from 'react'
import { reduxForm, Field } from 'redux-form'let InputField = ({ input, meta }) =>
<div>
<input {...input} type='text' />
<span className='error'>{meta.error}</span>
</div>let ExampleForm = ({ handleSubmit }) =>
<form onSubmit={handleSubmit((values) => someAction(values)} >
<Field name='fName' component={InputField} />
<Field name='lName' component={InputField} />
<Field name='email' component={InputField} />
<button type='submit'>Submit</button>
</form>ExampleForm = reduxForm({
form: 'exampleForm'
})(ExampleForm)export default ExampleForm
Validation
One nice thing about using Redux-Form is that it wraps your components with props that allow a validation to happen seamlessly. In Erik’s video, he passes the entire form a validate
method which is passed your form’s values and returns an error object with any errors:
const validate = (values) =>
let errors = {};
if (!values.fName) {
errors.fName = 'Please provide a first name'
}
if (!values.lName) {
errors.lName = 'Please provide a last name'
}
if (!values.email) {
errors.email = 'Email is required'
}
return errors...ExampleForm = reduxForm({
form: 'exampleForm',
validate
})(ExampleForm)export default ExampleForm
The validate
method can be passed to the entire form so all values can be validated at once or you can pass a validate method to each individual Field
component.
This is nice because you can break these components out into even smaller components that have their own built in validation methods so your core form component appears very clean but is actually quite sophisticated and self-contained.
Normalization and Formatting
A final point I want to touch on is how React-Form makes normalization and formatting data really convenient.
In Terrapin, we have an input that is built for price. It stores price in our database and in our Redux store as an integer but we display it to the end user as a currency (i.e. $0.00).
Redux-Form made it really easy to do this because the Field
component accepts methods called normalize
which runs input.value
through a function and then stores the result in Redux. It also has it’s counterpart format
which runs the value in Redux store through a function and then stores it as input.value
.
<Field name='price'
normalize={getDigitsFromValue}
format={toCurrency}
component={RenderPrice} />
Conclusion
Making and working with forms are really easy and straightforward when working with simple applications. Once your application starts becoming more complex, you start passing props and state around, things can get complicated and messy fast.
An easy and powerful solution to these problems is to incorporate React-Form to manage the hard parts of your form like state, validation, and formatting. It reduces the complexity of your app and makes extracting values from your form a piece of cake.