Using reselect for advanced validation in redux apps

Roman Damborský
6 min readJan 19, 2017

--

There are many validation libraries out there. Unfortunately, most of them are limited or, in a worse case, limit you. For complex forms and dynamic validation rules, we can use what a lot of us already have in stack — reselect library.

Details about how reselect fits into validation and what you can do with it are described at my github

Here, I’ll introduce couple of common validation cases, a little more advanced than those in ToDo / Sign Up / Login forms…

I use the word “form”, but the entire page could easily be considered. In one of our use cases, we have a “form” that is split among three panes (left hand menu, canvas, properties inspector) and we do validation across all these panes. This is one of the cases where idea of “form” gets a little bit out of hand.

Example app

Not too complex, yet still not so simple… You can see it running at heroku.

Consider a web app that allows to configure app to run in different environment modes. User adds configurations that consist of name, details file, and optional owner user. I only want to show how validation would work, so please, ignore any nonsense behind the app purpose or its UX :)

See how the validation emerged by following the commits history. By no means it is bug free, and there is a space for improvements on all fronts. The focus is really on the validation part — the rest of the app I put together quickly and in simpler ways.

Validation requirements

  • Application name has to be filled in
  • There has to be at least one valid configuration
  • If there are additional configurations, those could be partial
  • State of each configuration is shown next to its name, on left menu
  • Owner user field is validated asynchronously, showing loading status
  • Suggestions should be shown to user (orange text)
  • Form cannot be submitted unless it is valid or validation finished
example app validation

Initial state

When the app opens, user has to enter Application name and he is prompted to add at least one configuration. We highlight required field and show additional information in footer, next to the disabled submit button.

First configuration being added

After user click on “New Config” button, additional fields are shown. He enters “Development” as configuration name. There are two more fields to fill in — optional properties descriptor file and owner user. Owner user can be either empty or has to be set to valid email, verified through API call. User can see overall status of configuration in configs menu, and a hint next to the submit button. Submit is still disabled.

Valid form, ready to be submitted

Development configuration is all set, as well as Production configuration. There are two more configs with some issues — Stage and Test. Even though, we have at least one config valid and submit is now enabled.

App structure and reselect on the scene

There are two containers — AppContainer and Configs. For demonstration purposes, there’s also Config component.

AppContainer includes name input, main area and footer with submit button. It is connected to redux store and utilizes some of the validation results through decoration selectors (more on this on github).

Configs container handles left menu rendering and decoration, displaying currently selected config, and adding of a new config. It also constructs props necessary for Config component (field values, actions, decoration).

For more advanced use cases and ideas, check the repository or reselect documentation.

High level design

In your container or component, you usually want to do some sort of highlighting or show validation messages. This is available through selectors and mapStateToProps

class SomeForm extends Component {
render() {
const decor = this.props.validationDecoration;
const message = decor.nameMessage || null;
return (
<div>
<input onChange={ ... } className={ decor.nameClasses }/>
{ message }
</div>
);
}
}

const mapStateToProps = state => ({
name: nameSelector(state),
validationDecoration: formDecorationSelector(state)
});

formDecorationSelector is just a regular selector, depending on either validation selectors, or selectors already providing some portion of your state to your components:

const nameSelector = state => state.form.get('name');const isNameValidSelector = createSelector(
[nameSelector],
(name) => {
return name && name.trim() !== '';
}
);
export const formDecorationSelector = createSelector(
[isNameValidSelector],
(isNameValid) => {
return {
nameClasses: isNameValid ? '' : 'error',
message: isNameValid ? '' : 'Name has to be provided'
};
}
);

If you need more specific messages, you can return them from validation selectors, or use constants, I18n, code names…

Since you get access to complete state in selectors, you can leverage that and add validation specific reducers, to help out with validation state and progress. Async validation is also much easier to do, using these reducers:

export function validation(state = initialState, action) {
switch (action.type) {
case types.NAME_VERIFICATION_REQUEST:
return state.validation.
set('nameVerified', false).
set('nameVerificationRunning', true);
case types.NAME_VERIFICATION_SUCCESS:
return state.validation.
set('nameVerified', true).
set('nameVerificationRunning', false);

And then add another selectors using these data:

const isNameVerifiedSelector =
state => state.validation.get('nameVerified');

What didn’t work

Before reselect, we tried different approaches, but non worked well for our use cases.

In non-react parts of our app, we were using Parsley.js, which is a great framework if you like to set your rules in markup. But once you need to have partial validation, or validation triggered on some condition, things get funky very fast and you markup gets unnecessary level of complexity. Testing is almost unrealistic and you’re left to integration testing with Selenium, or similar beast.

Once we started playing with react and redux, we wanted to avoid those issues. The very first thing we had was top-down validation, where whole form was validated from a reducer. Basically, there was a function called from every case statement that had to do something with validated fields. It would provide everything the form would eventually need and then components and containers would pass the validation results down the props chain. You can imagine how the props passing looked like, or the troubles if you wanted to split reducer into several smaller ones. Validation would have to split, or be called across reducers. We didn’t want to be forced to shaping reducers to fulfil validation requirements.

We also experimented with bottom-up approach, which felt a little better. Each component or container would take care of it’s own validation. The logic would be local to component and it would reuse validation from lower, included components. But there was an issue with propagating data to parents. And also with the initial validation. You had to go into component/container and add bunch of handlers, conditions and adjust component lifecycle, to work with validation. This was not maintainable in a long term. You could also easily run into infinite loop, since at some cases, you had to call your parent in early phases of component lifecycle. This was also going against redux principles and one directional data flow.

--

--