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

Field enhancers #217

Open kettanaito opened 6 years ago

kettanaito commented 6 years ago

What

I suggest to add the possibility to extend the form or field components using enhancers.

Why

There are a lot of great features planned for the next release of RAF. However, I don't want to ship even great things in a mandatory manner. The end developer may not need those features like formatting or masks, but will be forced to download them within the source files.

How

Instead, I suggest to add the middleware support. I imagine it working in two scenarios.

Create field with enhancers

import { createField, fieldPresets } from 'react-advanced-form';
import fieldFormatter from 'react-advanced-form/enhancers';

class CustomField extends React.Component {}

export default createField({
  ...fieldPresets.optionalPresetHere,
  enhancers: [fieldFormatter]
})(CustomField);

Apply enhancers to the existing field

import { enhanceField } from 'react-advanced-form';
import fieldFormatter from 'react-advanced-form/enhancers';
import Input from './components/Input';

const CreditCardInput = enhanceField(fieldFormatter, secondEnhancer, ...)(Input);

Keynotes

kettanaito commented 6 years ago

Specification

General statements

  1. Enhancer is a pure independent function which appends certain functionality to the field/form.
  2. No enhancers are provided to fields by default.
  3. It is possible to use multiple enhancers on the same field.
  4. Enhancer has access to specific field lifecycle events to alter them the way enhancer intends.

Technical

  1. Enhancers must be imported explicitly to include only those which are required by the project, without polluting the end bundle size.
  2. Enhancer can be provided as the enhancers option of the createField() HOC.
    export default createField({
    enhancers: [enhancer1, enhancer2, ...]
    })(FieldComponent);
  3. Enhancer can be applied to the already existing field using applyEnhancer function:
    
    import { applyEnhancer } from 'react-advanced-form';
    import enhancer1 from 'react-advanced-form/enhancers/enhancer1';
    import enhancer2 from 'react-advanced-form/enhancers/enhancer2';
    import { Input } from '...';

const CreditCardInput = applyEnhancer(enhancer1, enhancer2, ...)(Input);

kettanaito commented 6 years ago

Update

Performed a very basic PoC to see how this would behave.

So far settled with the interceptors approach, where each essential field even could be intercepted and changed by enhancers. This means the introduction of an abstraction layer above the field updates on the form level, to prevent the event payload mutations within custom callback methods (enhancers apply transformations at the very end of the field update cycle, which is form update handlers).

I need to think more about this approach, there may be more efficient solution to this.

kettanaito commented 6 years ago

Considering #216 in the future scope, enhancers must be implemented in a way to work for both formful and formless fields. That means, that the interception of certain field events may be moved to the Field level.

I am not sure how I see this change. Interception on a Field level means alteration of the input data. I would prefer for enhancers to alter only the end-point behavior (up to Form.updateField()), preserving the original input data.

Another solution is to handle interception point at a different place depending on the formless status of a field. This way enhacers of formful fields would intercept events on a form level, which is proper, and formless fields would have the interception logic on a Field level, which is proper for that kind of fields.

kettanaito commented 6 years ago

Update

Interception of dispatched events' data was made possible via Observable.map():

Observable.fromEvent(...)
  .map(eventData => this.interceptEventData(eventName, eventData))
  .subscribe(func);

Technically speaking, there is no interception as such, but a mapping of incoming events to the listener passed through the array of interceptors (remapers). This works great on a form-centric interception concept, but is questionable whether this is going to be a solution for formless fields.

kettanaito commented 6 years ago

Since field enhancers are most likely to land in 2.0, I would consider to finish first the createField API change suggested in #281.

kettanaito commented 5 years ago

This proposal is obsolete and must not be implemented.

Please use createField API to achieve custom field behavior. Thanks.