Complex form validations made simple with React + MVVM
Form validations can be hard because, even with a small form, there are many combinations the state of the form can be in. MVVM is particularly good on these scenarios since it provides an Excel like experience. In Excel the cells reference each other and react to changes in the referenced cells. You change a value and other cells change accordingly.
Excel offers a simple model because you don’t have to keep track of who depends on what. You don’t have to specify something like “When this cell changes, increment the value of cell A by 1, update the status of cell B, etc.” This is how MVVM works and we’re going to take advantage of it to simplify complex validations on a form.
We’re going to create a form that has a enough moving parts to make it interesting but not overwhelming. Check out the live example to see what it looks like and behave. You can check out the source code for reference too.
The stack uses .Net Core, React, ViewModel, Semantic-UI, Webpack, Jest, Enzyme, among other things… I can’t believe you need so much to make an app, but at least we get a truckload of benefits like server side rendering (SSR), hot module reload (HMR), testing, code coverage, code splitting, lazy loading, etc.
Lets get started. Here’s what the form will look like in the end:
There are two sets of rules: data validation and UI.
- Username must not be empty and unique (checked asynchronously).
- Email must pass a basic email check.
- Password must be at least 8 characters long.
- Show a list of error messages at the bottom of the form. Hide the list if there are no errors.
- When the user hovers over the sign up button, any failing field will be red. The button will also turn red if there are errors.
- The sign up button will turn blue if all fields are valid.
- The username field will display a spinning wheel while the async check happens.
Our form will use Semantic-UI but of course it’s only for demonstration purposes only. You can apply this with whatever CSS framework you use, 3rd party or homegrown.
This is the template we’re going to give life:
It may look like a lot but it’s actually pretty simple. It has 3 field sections for the username, email, and password, the button, and the list of error messages.
Lets begin with the username property. With ViewModel you typically define a property by giving it a default value, like a blank string:
Let’s break it down.
ViewModel.property.stringinitializes the property with an empty string.
.notBlankis just a shortcut for
.validate(value => !!value).
invalidMessageis the message we want to display if the value isn’t valid (there’s the
validMessagecounterpart but we’re not using it).
validatingMessageis the value we’ll display while the property is in the process of being validated (asynchronously).
validateAsyncis the method that performs the asynchronous check on the value of the property.
In this example we’re going to fake calling the server to check if the username is available or not. We could put that logic straight into the component but it’s better to put it in a separate container (available to other components). We’re going to put that logic in a mixin container:
Mixins are the Swiss Army knifes of ViewModel. You can use them as traditional mixins, adding the properties and methods to the component. Or you can use them as services (Angular style), adding a property/object to the component which has the properties and methods in the mixin.
Using it as services is typically better than using it as mixins so we’re going to add a property called
userSvc which will reference the mixin
Great, we now have a property that validates it has some value and that it passes an async check. At this point we better add some tests to appease the testing gods:
We’re delegating the email validation to a service/mixin because that’s something we’re likely to reuse somewhere else.
The tests for
username property but synchronous:
Our password property is simpler:
Now we need a boolean property to know if the user is hovering over the button and a method to create the account:
At this point our component looks like this:
We have the properties and methods of the view model. All we need to do now is wire the “template” (HTML rendered) with their corresponding properties and methods. Let’s see a few examples.
Bind the user name input box with the
Show the error list if the view model is in an invalid state:
li element for each invalid message:
Test your app not the framework
One of the best features of MVVM is the ability to test that your bindings are properly wired, not that the framework is doing its job. The tests look like this:
How’s that better than snapshot testing?
The problem with snapshot testing is that any change in your component will break all the tests for that component. Here we can add elements to the component and our tests will continue passing. The tests will only fail if you break the contract between the HTML and the view model.
But you’re not testing every little detail of the component!
That’s true and by design. 99.99% of the time I don’t care if an element is by itself or surrounded by a
div, if I add a period at the end of a sentence, if I add an id to an element, etc. If there is a part of a component which isn’t bound but it’s very important to get it right, I then add a test for it.
The final component
The thing to note is that every property and method is its own island, and just like in Excel the bindings reference the properties (other cells if you will). We’re not manually updating values and keeping track of what depends on which.
Got any questions or comments?
Let me know in the comments section =)