hookstate icon indicating copy to clipboard operation
hookstate copied to clipboard

WIP: Fluent Validation Plugin

Open parisholley opened this issue 4 years ago • 4 comments

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()} />

parisholley avatar Aug 23 '20 05:08 parisholley

Codecov Report

Merging #97 into master will not change coverage. The diff coverage is n/a.

Impacted file tree graph

@@           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.

codecov-commenter avatar Aug 23 '20 05:08 codecov-commenter

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.

parisholley avatar Aug 23 '20 20:08 parisholley

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

parisholley avatar Aug 25 '20 00:08 parisholley

"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.

avkonst avatar Aug 26 '20 09:08 avkonst