Have you ever heard about the great scalability of react applications and how easy it is to maintain the code divided into small reusable components? You probably have, and it’s true! It’s true in both theory and practice, but the latter is not that easy. And here is a real-life example showing why it’s sometimes not as simple as you might think.
Here in Callstack, we are working for EEDI building an educational web platform. In the first few
Our requirements were as follows:
- validate on each change in a field
- enable validation of all fields on submit
- specify validation rules in an easy
way ensurevalidation works with all existing forms in the app
Seems easy, right? We just had to decide how to do that. Our minds were full of tweets and articles we’d read about React patterns, and it wasn’t an easy choice.
These are the most obvious solutions:
- using some third-party validation library
- create a simple
validatehelper — nothing fancy, just a function
- use a Higher-Order Component which would expose validation functions
Redux-form is a big but very stable validation solution for react. It’s based on Redux, and it requires you to keep your whole form state there. It gives you a simple way to “upgrade” your reducers to work with form data. You just have to combine the new reducer from the
redux-formpackage and use the
reduxForm HOC to get access to form data and errors.
Formsy-react is quite easy to use and contains a set of basic validation rules like
isEmail. It allows you to extend your own
input components to works with formsy. To achieve it you can use a mixin or a special HOC. It’s easy but it requires you to extend all your
Formik gives you a
withFormik Higher-Order Component, which you can use to wrap your form to give it access to errors data inside. It’s powerful and easy. Version
0.9.0 enables you also to render your form inside
<Formik /> component but a few months ago when we were fighting with validation it wasn’t implemented yet. One of the biggest pros of this library is full support for React Native. It’s also worth to mention that it’s only 9.2 kB.
The simplest solution you can think of — just two useful functions in a separate file:
validateField for validating particular fields and
validatefor validating the whole form. Both accept some validation rules in the following format:
(value: *) => boolean
An example usage of such a solution could look like this:
Higher Order Component
What if we create some abstraction over our helpers, as a Higher-Order Component? Creating your stuff as HOCs is (or maybe was) trendy… and useful. This component could expose a
errors over the props. This is how it could be used:
On that note, let’s look at some existing form code in our EEDI application.
Here’s how it looks like:
So what do we have?
- two inputs and one button —
- the requirement that the email input value should be an email and that the password is required — a harder but still easy thing we can achieve choosing one of our three solutions
- no Redux (form state inside the state of the component — this could be problematic)
- little time
- a lot of existing code we do not want to break
First of all, we would have to eliminate
redux-form for an obvious reason — no Redux.
Unfortunately, that’s not everything. Two of our solutions are HOCs (the
formik library and a custom HOC). Like I said earlier, we like HOCs, so this should be the best option for us. It should, but those Higher-Order Components need access to the form data to validate it. The component we want to wrap by a HOC has this data inside its state. It will be necessary, then, to create some stateful container with the data and pass the data to our form wrapped by HOC. Looks like a lot of refactoring. Refactor is always good but remember — we had “little time” and “a lot of code we did not want to break”!
The two options left were thus helpers and
Input component from our real-life example? It’s from our different “styleguide” repository. If it’s not necessary, we try not to touch it. That’s why we can’t use
formsy, which requires changes in input components.
Got a little nervous? There’s one solution left now — a simple helper.
Looks like it meets the requirements (yeah!). But there is one problem. We are very lazy developers. We didn’t want to collect errors each time by ourselves and write a lot of duplicate code.
There should be some better option…
Function as child to the rescue
Have you ever heard about a
function as child/
render prop pattern? Basically, a
Function as Child Component is a component that accepts a function as its child (or as some other prop) and passes some data through arguments of this function. If you want more details I’d recommend a nice talk by Michael Jackson:
Which problem does it solve? Accessing form data. It gives you similar power to a Higher-Order Component but it doesn’t require you to wrap anything and to create new components just to pass the required data into it. We can use it inside our existing form and pass form data from state as a regular prop. Just like this:
We created a simple
Form component which accepts
rules is an object with field name as the key and a validation function as the value.
fields is a similar object but as values we use the data from the form inputs.
onChange is just a simple callback. All the important things like
errors and the
validate function are exposed to the rest of the form via the arguments of the child function.
This is our implementation:
…and its usage inside a real-life example:
…and a live example if you want to play with it a little:
We know this is not the best solution (especially in case of performance) but it is the best which meets our requirements and allow us to introduce validation easy and fast.
It wasn’t an easy process to find the best solution, but such is life. The world with clear and with beautiful code, projects without changing requirements simply does not exist. The only thing we can do is continuously learn and try to use this knowledge in our projects. Don’t be afraid of using some new code patterns and not obvious solutions, just be careful!