kettanaito / react-advanced-form

Functional reactive forms. Multi-layer validation, custom styling, field grouping, reactive props, and much more.
https://redd.gitbook.io/react-advanced-form
MIT License
217 stars 24 forks source link

Resolver args for form rules or FormProvider rules #391

Closed jetpack3331 closed 5 years ago

jetpack3331 commented 5 years ago

I can't find it in the documentation but it's possible somehow provide to the Form rules or to FormProvider the same resolver args to the rules?

So the resolver function will return the same parameters (not only value but even fieldProps, fields, form) as for the field resolver the https://redd.gitbook.io/react-advanced-form/components/field/props/rule#resolver-function ?

I need to make a dependant/smart async rules for application wide forms and don't want to write a specific rule for my every input in the app.

kettanaito commented 5 years ago

Hi. Thanks for a question. If I understood your question correctly, you want to provide an application-wide rule or asyncRule validation rules for all inputs.

Field.props.rule

There is no need to provide this application-wide. This prop is designed to be explicitly set on individual inputs to provide per-instance validation. Application-wide synchronous rules can be defined in a validation schema and provided to either FormProvider or Form components:

const rules = {
  type: {
    // Applies to all inputs [type="email"]
    email: ({ value }) => validate(value),
  }
}

// To affect the entire application
<FormProvider rules={rules}>...</FormProvider>

// To affect a specific form
<Form rules={rules}>...</Form>

Field.props.asyncRule

Async rules are designed to be instance-specific and cannot be declared application-wide using plain library's API. Original motivation for this was a low level of reusability of async validation functions, as they seem to be tightly coupled with each individual input.

Perhaps, we can revisit this design limitation in the next version of react-advanced-form. Thinking of it now, I don't see any harm in defining general async validation rules (in the end value and other things are parametric anyway).

That doesn't mean you cannot provide them application-wide at the moment. You can use custom React context and teach your fields to subscribe to that context, prefilling the value of asyncRule of each input instance.

// pseudo-code
const AsyncContext = React.createContext({
  type: { email: async ({ value }) => await validate(value) }
})

// application root
<AsyncContext.Provider>
  {/* your app */}
</AsyncContext.Provider>

// Input.js
const Input = (props) => {
  return (...) // nothing unusual here
}

export default createField({
  mapPropsToField: ({ props, fieldRecord }) => {
    return {
      ...fieldRecord,
      // figure out a logic to grab relevant rule resolver from context.
      // Using "mapPropsToField" you can set the value of "asyncRule"
      // of all instances of Input created.
      asyncRule: getRelevantRule(AsyncContext)
    }
  }
})(Input)

Let me know if these suggestions are helpful.

jetpack3331 commented 5 years ago

These rules i know and the schema :) But i want in the args to be these properties fieldProps, fields, form when i'm using the resolver function in the rules for the FormProvider or for the Form. Now there is only value prop but we are using some selects for countries and i need to apply the rules based on the selected value in this Select.

Because we have looooot of forms and inputs and i don't want to add these rules specially in all of these inputs. I want to make it smarter and easier in the one place. Which can be FormProvider.

kettanaito commented 5 years ago

Okay, I think I'm slowly getting it. Could you please include an example of what you are talking about?

The way I understand it now, is that some resolver is not given enough parameters.

jetpack3331 commented 5 years ago

Of course

<Form={{
    extend: true,
    type: ({ value, formFields, form }) => {
        // Here i want to make a clever rule for form based on the value from form\
        // also if only contain field like formFields['countryISO']
        // Then i want set some rules based on this selected value for this form
    }
}}>
    {children}
</Form>
kettanaito commented 5 years ago

Your usage of validation schema is incorrect: the type selector accepts the map of { [fieldName]: resolver }, but you provide resolver function right away.

In-form dependencies

If you target a specific field you can base its resolver logic on other fields:

const rules = {
  extend: true,
  type: {
    // provide the type of a field
    email: ({ value, fields }) => {
       return fields.countryISO.value === 'FR' ? validateOne(value) : validateTwo(value)
    }
  }
}

<Form rules={rules}>{...}</Form>

This approach is recommended if all the dependencies for a validation resolver live within the form's scope (in a field state, fields, or form).

The very same principle applies to name selectors. You can learn more about the Validation schema in the documentation.

Out-form dependencies

When the dependencies for a resolver logic live outside the form (i.e. not present in field, fields, or form, but acquired from elsewhere—store, other components, etc.), I would recommend to keep validation schema in some global state container (i.e. Redux), and communicate with it via Flux pattern. Subscribe a Form or FormProvider component to the schema from the store and it will apply a different schema once it's updated in the store.

import React from 'react'
import { connect } from 'react-redux'
import { FormProvider } from 'react-advanced-form'

const App = (props) => (
  <FormProvider rules={props.rules}>
    {props.children}
  </FormProvider>
)

export default connect((state) => ({
  rules: state.rulesInState
}))(App)

Note that in both cases you have to specify a proper field selector per library's spec.

kettanaito commented 5 years ago

Please, @jetpack3331, have my suggestions helped to resolve your issue?

jetpack3331 commented 5 years ago

@kettanaito yeah. I mapped it to the

Thx!