Form Validation As A Higher Order Component Pt.2

High Order Component Form Handling

Introduction

This is the second and final part on implementing a higher order component for validating form inputs. Part one was solely focused on implementing the validation itself and not concerned with React or higher order components. For a better understanding of the validation implementation, you might want to read the first part, although you can follow along if you’re only interested in the React part.

In this follow up we will use React and Recompose to build a higher order component that accepts a function or a class containing form elements and take care of validating the inputs and returning and rendering the errors when needed.

The Basics

To simplify things let’s begin with a stateless functional component that renders a form containing two input elements, name and random.

const SomeForm = ({ form, onSubmit, errors = {} }) =>
<div className='form'>
<div className='formGroup'>
<label>Name</label>
<input
type='text'
value=
{form.name}
onChange={e => e} // not defined yet.
/>
{ errors.name }
</div>
<div className='formGroup'>
<label>Random</label>
<input
type='text'
value=
{form.random}
onChange={e => e} // not defined yet.
/>
{ errors.random }
</div>
<button onClick={() => onSubmit(form)}>Submit</button>
</div>

So we have a component now, that expects a form object, an onSubmit function and an errors object and renders errors if any exist for a given key. What we haven’t implemented yet, is the onChange handling for the inputs. Before we go into depth on how to implement the change handling, we will take a look at our current getErrors function.

Part one was mainly focused on writing functions that take care of handling validation rules objects and inputs and returning an error object.

import R from 'ramda'
import
Either from 'data.either'

const
{ Right, Left } = Either

const makePredicate = ([predFn, e])
=> a => predFn(a) ? Right(a) : Left(e)

const makePredicates = R.map(makePredicate)

const runPredicates = ([input, validations]) =>
R.map(predFn => predFn(input), makePredicates(validations))

const validate = R.map(R.compose(R.sequence(Either.of), runPredicates))

const makeValidationObject = R.mergeWithKey((k, l, r) => [l, r])

const getErrors = R.compose(validate, makeValidationObject)

Now that we have the validation functions and the form in place, our next step will be to implement the high order component that connects the two together. We also need to figure out how to manage local state, which we will also solve in the following section.

Implementing the Higher Order Component

Instead of writing our own higher order component from scratch, we will simply use Recompose to build a wrapper that knows how to accept rules and inputs and finally handle local state for a passed in function or class component.

The most straight forward approach is to use withState from the aforementioned library, as it solves the local state problem out of the box.

withState('state', 'updateState', initialState)

This is already perfect for our case. State could mean the form inputs as well as the errors object in our case. While we’re at it, let’s start with a basic implementation and see how far we can get with this initial try.

const HocValidate = (initialState, validationRules) =>
withState('state', 'updateState', initialState)

One thing we notice when looking at the above example, is that we don’t know where to process the validation rules. The initial state is clearly the default form data, while the rules can’t be applied anywhere.

We will use two more functions from Recompose, compose and mapProps, that should help us to solve the problem with where to handle the rules. On a side note it’s also interesting to mention, that we could also use compose from the Ramda library if we like, both compose functions are interchangeable.

const HocValidate = (initialState, validationRules) => compose(
withState('state', 'updateState', initialState),
mapProps(({ updateState, state, ...rest }) => ({
// onChange
form: R.prop('form', state),
errors: R.prop('errors', state),
...rest,
}))
)

Defining the form and error props to be passed into the wrapped component means accessing the corresponding properties inside the local state. We could also have passed in, the complete local state, but to make things more clear it would make sense to separate the form input values from the errors. What is missing is the onChange function that we need to pass down to the form component, so that the local state can get updated whenever a change occurs.

mapProps(({ updateState, state, ...rest }) => ({
onChange: R.curry((name, value) =>
updateState(state => {
const newState = R.assocPath(['form', name], value, state)
const errors =
R.map(
ErrorComponent,
getErrors(R.prop('form', newState), validationRules)
)
return R.assoc('errors', errors, newState)
})
),
// ...
}))

Let’s take a closer a look at onChange. We call updateState with the new state, that we calculate depending on the key/value pair and the current state. key/value are passed in when calling the onChange via the wrapped component inputs.

We’re also using a couple of Ramda functions here, just for convenience. assocPath is a nice way to update a nested object, we’re updating the state by accessing the form property and updating the property specified via the key with the passed in value.

Things get really interesting when we connect our getErrors function with the predefined validationRules and map the results onto an ErrorComponent. We will get back to this component in a minute. Finally, we also update the errors inside the previously calculated state with the result from calling getErrors and return that new state.

Rendering Errors

One aspect is still missing. The error rendering. As already mentioned, we have an Error Component, that expects an Either, and takes care of returning the proper representation of an input validation.

const ErrorComponent = result =>
result.cata({
Right: a => null,
Left: errorMsg => <div className='error'>{errorMsg}</div>
})

If you have read part one, then this should need no further explanation. Incase you haven’t read it or are wondering what the above actually means, to quickly summarize the facts: our getErrors function returns a map containing an Either instance for every given form field. The advantage is that now we can simply map the results and call ErrorComponent with an Either instance. ErrorComponent will return the right UI representation depending on the fact if the validation was successful or not by simply calling the cata method on the Either instance.

Final Adaptions

To finish off the example, all that is left to do is implement the onChange method inside our stateless function.

// helper
const getValue = R.path(['target', 'value'])
// ...
<input
type='text'
value=
{form.name}
onChange={R.compose(onChange('name'), getValue)}
/>

As soon as a change occurs we call onChange with the specified field name and the value. getValue is just a helper function that we can compose with onChange. We could even refactor this composition, but this should suffice for the current example.

Actually, this all we need to create a reusable higher order component for common form validations. To wrap it all up, here is an example with the complete code.

Outro

There are some refinements we could tackle with the current implementation, including enabling to handle nested form structure validations when working with multi forms for example. All in all we should have gained some general insights on how to create our own higher order component for form validation handling.

Also read Part 1, if you haven’t.

Any questions or Feedback? Connect via Twitter

Links

Recompose

Ramda

Folktale Either