Reactive validation
If you have reliable and flexible reactive primitives, why not use them?
Reactive validation is a feature of the validation callback that tracks changes in atoms used inside it under the hood, as if it were a true computed value (tl;dr, it is computed). This enables the magical ability to recalculate the field’s validation atom based on changes in the field’s external dependencies.
Overall, this process can be divided into three logical parts:
- first call of the
validatecallback: for dependencies to start being tracked, the callback needs to be called once to collect them, which can be implemented by validation triggers such asvalidateOnBlur, for example; - dependency tracking: subsequent calls to
validateoccur due to dependency changes; - applying changes: the result of the callback invocation applies changes to the
validationatom according to the callback’s behavior, as ifvalidation.triggerwere called.
Interesting fact: all of this is implemented through dynamic creation of an
effecton eachvalidation.triggeraction call, which lives exactly until the nextvalidation.triggercall and in which thevalidatecallback is invoked. Dependencies inside this callback become the effect’s dependencies, then the effect applies changes to the field’svalidationatom.
Let’s implement a simple user registration form model:
import { reatomField, reatomForm } from '@reatom/core'
const form = reatomForm({ username: "", password: "", confirmPassword: reatomField("", { validate: ({ state }): string | undefined => form.fields.password() != state ? "Passwords do not match" : undefined, }),}, { name: 'registerForm', validateOnBlur: true});In the validate callback of the confirmPassword field, we use the value of the password field: this is where the subscription to it happens. Now, on the first validation of confirmPassword (and for the form it’s defined that all fields are validated on blur validateOnBlur: true), the confirmPassword field will be revalidated on every change to the password field.
We can also choose not to subscribe to the password field immediately, but first check that the confirmPassword field is not empty:
import { reatomField, reatomForm } from '@reatom/core'
const form = reatomForm({ username: "", password: "", confirmPassword: reatomField("", { validate: ({ state }): string | undefined => { form.fields.password() != state ? "Passwords do not match" : undefined if (!state) return 'Confirm password is required'; return form.fields.password() != state ? "Passwords do not match" : undefined; } }),}, { name: 'registerForm', validateOnBlur: true});This allowed us to avoid an unnecessary subscription to the password field while the confirmPassword field is empty.