hookstate
hookstate copied to clipboard
WIP: Fluent Validation Plugin
Potential implementation direction as a solution to #94 and maybe introduce a new Fluent API abstraction.
Problem:
- Need a way to support running validation in child components yet maintaining isValid() state in a parent component. Example, multiple tabs with a "error badge" if any of the state displayed within the tab is invalid (and doing so in a way that doesn't cause a full re-render).
- Need a way to validate data within an array whose length may change at any time.
Proposed API syntax:
ValidationAttach(state, (validator) => {
// conditional validation
const enabled = validator.notifications.when(n => n.enabled.get());
enabled.message.required();
// union type support (graphql example), conditional reuse
enabled.whenType('__typename', 'NotificationConfigEmail').subject.required();
enabled.whenType('__typename', 'NotificationConfigSlack').channel.required();
// nested array path support
validator.roles.name.validate(name => name && name.length > 0);
// inject any other parts of the state into conditionals
validator.properties.conditions.when((c, properties) => {
if (!c.propertyId.get()) {
return false;
}
return properties.some(p => p.id.get() === c.propertyId.get() && p.fieldType.get() === FieldType.SELECT);
}, validator.properties).valueId.required();
});
// validate entire state
Validation(state).valid();
// validate portion of state (eg: navigation/tabs)
Validation(state.roles).valid();
// validation multiple portions (eg: multiple fields on same tab)
Validation(state).valid(['roles','notifications']);
// downstream component can check if it is required or valid
const validation = Validation(state.field);
const form = <Input error={!validation.valid()) value={state.field.get()} required={validation.required()} />
Codecov Report
Merging #97 into master will not change coverage. The diff coverage is
n/a
.
@@ Coverage Diff @@
## master #97 +/- ##
=======================================
Coverage 90.83% 90.83%
=======================================
Files 1 1
Lines 480 480
Branches 132 132
=======================================
Hits 436 436
Misses 44 44
Continue to review full report at Codecov.
Legend - Click here to learn more
Δ = absolute <relative> (impact)
,ø = not affected
,? = missing data
Powered by Codecov. Last update c1585a1...862d582. Read the comment docs.
i made a attempt at extracting the fluent'ness but ran into some typing issues.. though it may be possible with right magic. also realizing as i go that my scope is increasing a bit (eg: marking things as required) which start to feel more like a form plugin that pure validation.
new api addition:
Validation(state.properties).conditions.when(({ propertyId }, properties) => {
if (!propertyId) {
return false;
}
const property = properties.find(p => p.id === propertyId);
return property.fieldType === FieldType.SELECT;
}).valueId.required();
the when
method comes in handy when dealing with form state with cross dependencies. i'm building a "dynamic form ui" which allows a user to optionally show a "property" based on either the existence of another property (eg: checkbox) or based on the value chosen for that property (eg: select box). to keep the UI smooth, i need to conditionally apply validation depending on if the property they are referencing is of a certain type, otherwise, ignore.
in retrospect, i don't remember why i actually needed to downgrade anymore.. any validation callbacks should be listening to the state via get() incase of changes... took it out and still works fine
"any validation callbacks should be listening to the state via get() incase of changes" - exactly - validation functions should read the state and leave traces of what they touched, so hookstate can detect when and what should be rerendered.