Transcript
This transcript was autogenerated. To make changes, submit a PR.
Hey, what's up? I'm Eva Tar. I'm a front engineer at Facebook
here in Tel Aviv, and I'm here to talk to you about Vest, which is
an open source framework I'm very excited about.
But before I begin, I just have to say, Vest is not a Facebook project
and it is not affiliate with Facebook. Just have to get this out of the
way. Vest is short form validations tests. So as you
can probably guess, Vest is a library intended for use with
form or input validations in JS apps.
And if you've done even a little form validations in the past couple of years,
you're probably familiar with the amount of form
validation libraries that occupy this space, and it's
only logical to ask, why create a new one? And that's a very valid
point that I usually tend to agree with. But in this case,
I think we can only answer this question if we actually cover the problem
and describe the way form validations works today.
So in a typical scenario, what we usually have is a user that goes
to a website, they face a form, let's say user
login form, and they type inside an input box,
and then event handler gets triggered. This event handler usually
calls a function that we wrote or some third party library
that contains a validation code. But this function usually takes
care of the validation. It decides whether the inserted input is
valid or invalid, and then it's up to us to decide how
to show that to the user with a red x or a green checkmark or
whatever. Now, this usually works
if we only have only a limited amount of fields in our form,
or if we do not plan on expanding and adding more features to the form
in the future. Because from my experience,
as long as our feature grows and the more
complex our feature gets, the more our form
gets more complicated and the validation code gets more complicated,
because what we usually tend to do without any specific
structure for form validations is put the validation code inside our
change handler, or inside our component, or inside our feature,
and then we do not have a way for describing it separately.
It becomes a part of a spaghetti code that's called our feature,
and we have no easy way to test the validation code,
we do not have an easy way to maintain it, and it's very hard to
read, very hard to write. And if we want to do,
to create a refactor in our feature, we have to basically
scrap our validations away and rewrite them from scratch.
And this is what Vesp tries to solve, and this is what Vesp
comes to handle. Vesp tries to come and suggest a structure
for formulation that's also separate from a feature code.
And the way Vesp does it is very easily by borrowing
the syntax and style of unit testing frameworks such as mocha or
jest, and implementing it in the world
of form validations. So basically you describe a form validations suite
that contains each and every test or each and every field in your
validation suite, and then it can easily separate it
from your feature code. It has a very narrow point of contact
with your code, and it supports most of the features you would expect from a
validation library. And actually I think a little more now,
I could talk about vest all day, but I think it would be best by
showing to you through code. So what I want to do now is show you
how easy it is to integrate vest into an existing form and an existing
feature without all the hassle.
And what we're going to do is take this form, which is a react form,
and it doesn't matter at all that it's a react form. It could be angular
or view, or even without a framework at all,
and add some validations to it from scratch. So it doesn't have any validations
at the moment. We're going to install vest to
our sandbox and create a new
file, and I'll call it validate js.
The name doesn't matter. I'll import vest from
vest, and I'll create my validation suite and export
it, because we have to run it from our form. So export
default vest create.
That's how we create a new validation suite, and we'll name it user form.
The name doesn't really matter. And I'll define
my validation suite body, and we get form validations,
the data to validate from our form through the arguments. So what I
do here is data, and I'll initialize it with
an empty object in case it doesn't get sent.
Now we have to write our test for form validations, just like in unit
test, we have a test function we'll
import test, and we'll also import enforce, which is pretty much like expect.
This is our assertion function enforce.
And I will do a dummy test just to see this stuff works. So I'll
do test, and the first argument is the name of the field we're validating.
So I'll do username. And now the second
argument is not a function like in unitest, but instead it's the
string of the message that we want the user to see in case of validation
failure. So here it's going to be like for example,
username is required. So username is
required. Okay. And in
the test body I'm going to do enforce data
username. So enforce username
is not empty.
So this is our first test ever. I'm going to go to the form
and I'm going to connect it. So the first thing I'm going to do is
I'll import my validation test, my validation suite,
sorry, import v form validations valid.
Oh, I called valid. Okay, I'll rename the
file valid.
Okay, I'll import v from validate.
And what v gives us is basically a function that when we
run it, our validation suite will run. So I'll
use it inside our change handler. And what we have in the change handler is
basically a state setter and it sets the states to
the key value pairs of our fields
in our form. So it's going to be for example current field, so it's username
and the value we put inside. So that's how our state looks.
I'll do v and I'll pass next state to it as well.
Now if we run it, nothing will happen even though the validation will run.
What we have to do now is display the validation result. So what
we can do here is constraint equals v
get and this is how we get the validation result from that v
object. So we take the result and
inside our input component and it's a simple react component,
it has nothing to do with best. I already added a validation
messages prop that takes an array of strings and displays
the first one as the validation message. So it's
going to look like form validations messages and it's
going to take an array of strings and this is the error we're seeing here.
So what I'm going to do now is instead of passing
a hard coded array, I'm going to res get
errors, username, the name code
we're validating. And what we're going to see now is that
when I remove the stuff that's written here, we're going to see username is required
because that's what we stated here. Now it's not
in red and I already have a class name in that input for
validation errors. So it's going to be like this.
And we can do this automatically with the best helper utility.
We can import class names from best class names,
import class names from best
class names and what it does it allowing us to specify which class names
we want to show in case of form validations success or failure.
So in our case let's do it like this,
it returns us a function. So we'll name
this function Cn. For example, const Cn equals class
names. It takes form validations result object so res
as the first argument and an object of key value pairs with
the names of the classes we want to show. So for example, in case the
form is invalid, let's do error.
Sorry. In case it's valid, let's do success. In case
it's invalid, let's do error.
And in case it's warning there is such a thing, let's do warning.
Now we take this again and put it inside our
input form and do class names.
Class name, sorry. And do cn
username. And what we'll see now is that it turns to green
because it's successful. And if we remove everything from it,
it's empty. So it's red. So this
was easy. Let's copy it over to the rest of the fields
in our form. So username
password let's also do
this for firm and
in terms of service. I don't want to actually have
a validation message because it already has some text. Let's just add
the class name for the color. So let's do tos.
Now let's quickly add some more validations for our username.
So let's do username must
be at least three characters.
Okay, longer than or equals three.
Now let's copy both these for password and
password must be at least five characters. In this case let's
do just longer than four. Okay,
username a must be at least three characters. Password is required.
Abcde. Okay, perfect.
Now let's do confirm test confirm
passwords do not match.
Let's do enforce data confirm
equals data password password
now let's see. Abcde.
Wait,
data confirm data password.
Why not? Oh, I have a typo. Here it
now let's do the same for terms of service. Test tos.
And as I said, we do not need a validation message here.
Let's just do enforce data tos
is truthy because it's a boolean value. In our
case it's checkbox perfect.
Now one thing I'm seeing in the confirmation box is
that when I remove the password and then I remove the confirmation,
it's green because they are equal, but they shouldn't be
green I think because it's empty. So let's fix
this if data password. So only if data password
is present. Let's run the confirmation.
So let's try this. Perfect. It's empty.
Now it doesn't have any color on it so it's not getting validated
at all if password is empty. But there is one more thing.
I type inside username, for example, and all the others get lit up.
We're basically validating all the fields, even though
we're only touching one of them, which is not a great user experience in my
opinion, because the user only touched the first one, it's obvious that the others
won't be filled yet. So let's
fix this. Let's do vest only,
which gives us a way to only validate a specific
field and which field we want to validate to
validate the field that the user is currently touching. So what
I want to do is get somehow the current
field and pass it
over to only. And here in our change handler,
we actually do get the current field. And again,
this is a simple react handler.
I'm taking this and I'm passing it over to
vest. And what I'm getting now is that only the field that
I'm touching is getting validated. The rest aren't cool.
Let's see now, this is
kind of a weak password. Let's say we
want to also suggest the user to add a number to make the password
stronger. We do not want to fail the validation on this condition,
but we want to suggest it. How would you do that?
With vest you have the worm optionality, so it's
going to look like this.
Test password again
and password is
Wic, maybe add a number.
Okay, and now let's do enforce
data password matches
and then we can pass a regex. So let's do a
simple number regex. And what we're going
to see here is that we're getting that validation
message. But I cant it to be orange. I want it to be a warning.
I don't want it to fail form validations. So what I
can do here is tell vest to put it in a different basket using
vest warn. So vest warn.
Vest warn. And if I try it again,
we orange. We're not seeing form validations message because back in
the form we asked only for the validation errors here. If you
see here get errors. So let's also put the warnings
inside. So it takes an array. So we can
either pass the errors array or the
warnings of array. In this case I'll just spread both those
for a single array and we're getting it perfect.
Now what about async validations? For example, what if we
wanted to test that the username is already present or already taken?
Sorry, we wanted to go to the server and see if
a user is already taken. This supports async validations.
In our case I already have a server call. It's a dummy server call import
from API
I have does user exist? It's a
mock server call. And what it does is basically
returns a promise that rejects when the user exists and
it resolves when the user does not exist. So what I'm
going to do here is write a new test for
username. Again,
username already taken. And what
this does is basically similar to how does user exist. It fails
in async validation if a promise returned rejects.
So if I have a promise here and it rejects, the validation will fail.
So data username.
And now if I try typing something inside,
for example a username that I know is taken.
We won't see anything because form validations is async. Might take a couple of seconds
and we're not rerendering react here once the validation
is complete. So what I want to do instead is
something different. I first want to display a spinner.
So I have a spinner prop on my input.
It's called pending. And what I want to
do first is when I make a change to the username field
just to show that spinner. So I'll create a new state for react.
Cant is pending,
set is pending equals use
state and default it false.
Okay, so if the current field is username, I want to show
that spinner oh,
set is pending to true. Perfect.
And here I can already decide to show pending by
is pending. So if I type
something inside, we're saying the spinner cool.
Now what I want to do is wait for the username
validation to complete and then disable that spinner.
So I'm going to take form validations result and what I'm getting
here from the function invocation is the validation result the
same one that I'm getting here in res. So what I'm going to do
is const res
equals v and then res.
And what I have here in my validation result is a function called done,
which basically takes a callback and runs it whenever the
validation is fully complete, all the async validations as well.
So what I'm going to do here is pass a callback that all it
does is sets is pending to false.
So what we're going to see here, for example Abc,
and now we can see that the validation is complete because the spinner
is gone. Now what happens if I use a username that's already
taken?
That's all there is to it. That's it,
we're finished with the async validation. But I
do see something else here. So if I
try typing inside an invalid username, for example AEA,
we're seeing the spinner, we're going out to the server even though we know that
the validation already failed for the username, the username is not valid to
begin with. We shouldn't go to the server in this case.
So vest gives us a way to pick into the uncompleted
validation to the intermediate validation result and
get validations that already failed or passed. So what we have
here is best draft, and what gives
us is the same validation result, only uncompleted. And what we have here
is has errors for username,
and this returns a boolean. In our case it will be true because
the username is too short. So only
if the username doesn't have any errors on it,
we'll go out to the server. So let's try again. Ea,
we're not seeing the spinner because we're not going out to the server. Okay,
let's try again. And the username
is valid technically. So we're going to the server.
But another thing here is that I'm going to the server again
when I'm fixing it, and if I remove it and
go back to the username that was there before, which is invalid,
will still go to the server, which is not a good idea because we
already know it's invalid, we already know it's taken on the server.
Vest allows us to memorize validation results using
test memo,
and we can rely on a dependency array and specify which
values we want to memorize by. So I want
to memorize by the username, which means that if the username
doesn't change, or if we get the same username again, we shouldn't go out
to the server at all. We will just use the same validation result that we
had before. So let's try again.
Data username.
So what we're doing here, we're testing memo
for the field username only if the username is
not changed. Otherwise we're getting the same validation results
that we already got before. So let's try again.
We're going to the server the first time,
we're going to serve it again, and we're not going to
the server third time because we already know
the user's name is taken. So what
we have here is basically this is all our form validations.
It's separated from our features logic. It took us about,
I think, five lines of
logic to add vest to our features, and a few more lines
to just display it. So as you've seen, vest is
a pretty powerful and a pretty robust framework. It has lots of features
and capabilities and we did not even cover all of those today. And yet
it still maintains all the complexity in different modules
and has a very narrow point of contact with your code. So you can make
refactors or change your feature or even replace your UI framework.
You cant even touch form validations code and worry breaking I'm
a big believer in the value vest gives you as a developer to
build your app faster with more confidence.
I've used vest personally in multiple production apps, others have too,
and I've received some positive feedback about Vest. Now I'm looking for
more developers to come and help me shape the future of vest.
And I'm also looking for more community adoption and people to come and
suggest new feature ideas to vest. If you
have any any question, you can ask me at Twitter or go
to the documentation page on that projects GitHub I'm
very much excited about best. I hope you like it too.
Thank you.