Form Validation As A Higher Order Component Pt.2

High Order Component Form Handling

A. Sharif
A. Sharif
Oct 16, 2016 · 5 min read


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

const SomeForm = ({ form, onSubmit, errors = {} }) =>
<div className='form'>
<div className='formGroup'>
onChange={e => e} // not defined yet.
{ }
<div className='formGroup'>
onChange={e => e} // not defined yet.
{ errors.random }
<button onClick={() => onSubmit(form)}>Submit</button>

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'
Either from 'data.either'

{ Right, Left } = Either

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

const makePredicates =

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

const validate =, 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

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, }) => ({
// onChange
form: R.prop('form', state),
errors: R.prop('errors', state),,

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, }) => ({
onChange: R.curry((name, value) =>
updateState(state => {
const newState = R.assocPath(['form', name], value, state)
const errors =
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

const ErrorComponent = result =>
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

// helper
const getValue = R.path(['target', 'value'])
// ...<input
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.


Also read Part 1, if you haven’t.

Any questions or Feedback? Connect via Twitter



Folktale Either

JavaScript Inside

All things JavaScript.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store