Created with CodeSandbox
For both of the above reasons, I'd really like a means to apply Joi validations in a React context.
I'm having trouble keeping sandboxes synced so I'm going to leave the latest link here
Joi is perhaps the most extensive validation library available in JS. It was originally baked into hapi.js, a production-ready, api framework. For both of the above reasons, I'd really like a means to apply Joi validations in a React context.
here is a link to the repo. It may work well, but I personally can't stand wrapping my components in this way. Its a hard pass for that reason. I'd much prefer to integrate the validation into a component to wrapping my components. That's just one issue I have with this.
The second issue applies more broadly to "form" validation. Having used 2 popular form validation options, React Hook Form and shitee... can't remember the name of the other. At any rate, they both feel "extra" -- too much of a learning curve and/or too much change in code flow/structure. Also, both of those options are react specific. While I love react, that doesn't help me with my server-side validations. I really don't want to write two sets of validation on my own dime. And don't get me started on controlled vs uncontrolled components...
Please note I'm not a Joi expert.
Specifically, it provides a single method, validate
which will validate the whole schema -- meaning that that there's no good way to just validate a single field/property (though one can use the abortEarly
property to make sure all fields aren't validated each time). To start, I'm going to ignore this problem and measure performance after I've built a decently complex form.
In react-hook-form they have a concept of controlled vs uncontrolled components. Uncontrolled components require extra specification and code. While this is doable, I found the idea confusing at first and possibly unecessary -- this is what I hope to find out.
I plan to
This was too convoluted because I'd have to place all those individual properties into and object to be validated against the schema. Instead, I've chosen to put the useState
for every property that needs to be validated and use change events to update that state's value. I don't know ultimately what this will look like. To start, I'm going as simple as possible and after I fiddled with it for a while, I imagine some patterns will present themselves -- allowing for some elegance and genericsm. But I will not be sacrificing simplicity, even if it means the code is a bit repetive and not as dry
as possible. While these things are good, they're not ultimately important.model
in state useState()
in the hook. Then when update
is called (see below), the code will update the model in hook state currentModel
by using the property
argument as the key. See src/forms/CreateUser.tsx for example invocations.
From the useValidate
call I'm returning (amongst other things) an update
which requires two arguments; namely, property
and value
.
While value
should be obvious, property
is worth a brief explanation: property
should correspond to a property on the Joi Schema
passed into useValidate
. It doesn't have to be, indeed there are no limitations in this regard. In fact, the property doesn't even have to be on the inital model passed in to useValidate
. It's my sincere goal to keep this as simple as possible and not bother with customization via the hook, but rather use patterns to implement complexity that can be passed thru/to the hook.
As of now, I'm trying a hook approach. the hook, useValidate
requires 4 arguments:
-
schema (Joi Object)
-
model (object that represent the model to validate).
-
options: See joi validate options arguments here
-
onSubmit: this function will be wrapped by
handleSubmit
(see Out section) and only execute whenisValid
. This is just a convenience wrapper.
- onSubmit should expect argument
model
which will be thecurrentValue
from the hook. It should reflect all properties of the origally-set object when the hook was created + any that may have been added (for better or worse) -- in other words, the aggregate of the original and updates. SeeuseValidate.tsx.update
for code.
- onInvalidSubmit: this function will be wrapped in the
handleSubmit
and will execute when statusisValid
= false. Should expect the arguments
errors
: an object of { [propertyName]: [message] } structure.joiError
: the original joi error objectmodel
: the latestcurrentModel
from hook state.
See ./src/forms/CreateUser.tsx
for current approach. useValidate
will return and object with the following properties.
- update -- the method used to update values to be validated against the schema. See
forms/CreateUser.tsx
for example - isValid --
true
if.validate
returnsundefined
, otherwisefalse
- currentModel -- the current state of the model originally passed in using
useValidate
. As mentioned above, using theupdate
method any property could be added. Incidentally, after eachupdate
the model itself is replaced by way ofassign
. SeeuseValidate.ts
for relevant codes. - errors -- an object with keys for each property and values for each error. As of now, it appears that with my configuration that joi will stop validating after finding the first error. This is OK for now. But, it could obviously be made configurable via and
options
object passed touseValidate
. - handleSubmit -- a function that will wrap
onSubmit
andonInvalidSubmit
in a function will invoke each one based on their correspondence to the current validation state, theisValid
value fromuseValidate.tsx
As of now, this solution doesn't offer a way to validate/invalidate a form in a "formal" way. I'm not sure that it could do that without a hack of some sort. My "hack" would look something like this:
<form>
<input type="hidden" required value={isValid ? "1" : ""}>
// will need to dynamically set message based on errors returned by hook
</form>
A little hook that will allow for using 'onchange' with a timeout. In this case, I've set it to 500ms, so after 500ms where the function hasn't been called the update
method will be called and consequently invoke the validate
method. I probably should move this to a separate sandbox and elaborate on it there.