2.1.4 • Published 3 years ago

riot-final-form v2.1.4

Weekly downloads
8
License
MIT
Repository
github
Last release
3 years ago

Riot Final Form

Easily implement final form in your Riot components.

Usage

npm i -S riot-final-form

Your riot component:

<some-form>

    <form>

        <div class="field">
            <label for="name">Name</label>
            <input type="text" id="name" name="name" />
            <div class="error"></div>
        </div>
        <div class="field">
            <label for="age">Age</label>
            <input type="text" id="age" name="age" />
            <div class="error"></div>
        </div>

        <div class="field">
            <label for="address">Address</label>
            <input type="text" id="address" name="address" />
            <div class="error"></div>
        </div>

        <div class="field">
            <label for="password">This element will be ignored</label>
            <input type="password" id="password" name="password" ignore />
            <div class="error"></div>
        </div>

        <div>
            <div class="col w-50">
                <button type='reset'>Reset</button>
            </div>
            <div class="col w-50 text-right">
                <button type="submit">Submit</button>
            </div>
        </div>
    </form>

    <script>

        import withFinalForm from '..';

        export default withFinalForm({

            onUpdated() {

                // this.finalForm becomes available after
                // component has mounted. This gives direct
                // access to the instantiated final form object
                const form = this.finalForm();

                if (this.state.likesCheese) {
                    form.batch(() => {
                        form.change('name', 'pepe');
                        form.change('age', 66);
                    })
                }
            },

            formElement() {
                return this.$('form');
            },

            // https://final-form.org/docs/final-form/types/Config#onsubmit
            onSubmit(values) {
                $api.post('/stuff', values);
            },

            // https://final-form.org/docs/final-form/types/Config#initialvalues
            initialValues: {
                name: '',
                age: null,
                address: ''
            },

            // https://final-form.org/docs/final-form/types/Config#validate
            validate(values) {
                const errors = {};
                if (!values.name) errors.name = 'name is required';
                if (!values.age) errors.age = 'age is required';
                if (!/^\d+$/.test(values.age)) errors.age = 'age must be a number';
                return errors;
            },

            // https://final-form.org/docs/final-form/types/FormApi#subscribe
            // https://final-form.org/docs/final-form/types/FormState
            onFormChange(formState) {
                const submit = this.formElement().querySelector('[type=submit]');
                submit.disabled = !formState.valid;
            },

            // https://final-form.org/docs/final-form/types/FormApi#registerfield
            // `subscriber: FieldState => void`
            // Omits `blur, change, focus` keys
            onFieldChange(field, { touched, error, valid, visited, dirty }) {

                const errorEl = field.parentElement.querySelector('.error');

                if (touched && error) {
                    if (errorEl) errorEl.innerHTML = error;
                    field.parentElement.classList.add('error');
                } else {
                    if (errorEl) errorEl.innerHTML = '';
                    field.parentElement.classList.remove('error');
                }
            },

            // https://final-form.org/docs/final-form/types/Config
            // validate, initialValues, onSubmit, and destroyOnUnregister cannot be overwritten. `destroyOnUnregister` is always true.
            formConfig: {
                debug: true
            },

            // can be one of: active, dirty, dirtyFields, dirtySinceLastSubmit, error, errors, hasSubmitErrors, hasValidationErrors, initialValues, invalid, modified, pristine, submitting, submitError, submitErrors, submitFailed, submitSucceeded, touched, valid, validating, values, visited
            formSubscriptions: {
                visited: true,
                dirty: true
            },

            // https://final-form.org/docs/final-form/types/FormApi#registerfield
            // `subscription: { [string]: boolean }`
            fieldSubscriptions: {
                name: {
                    pristine: true,
                    valid: true
                },
                age: {
                    submitFailed: true,
                    valid: true
                }
            },
            // https://final-form.org/docs/final-form/types/FieldConfig
            // Based on a name basis. If your field name is `nested.stuff[0]`, then your config is `{ 'nested.stuff[0]': { ... } }`
            fieldConfigs: {
                address: {
                    afterSubmit: () => console.log('afterSubmit yay!!')
                }
            }
        });
    </script>
</some-form>

Manually initialize final form:

There may be cases where you want to manually initialize FF, such as when you depend on an XHR request to load initial values. For these scenarios, you can use the manuallyInitializeFinalForm flag on your component, and manually trigger component.initializeFinalForm(); inside of a lifecycle hook. Be cautious not to lose the lexical this of the initializeFinalForm() function. If you need to deeply nest or pass a callback that later calls this, you can do so by extracting into self const self = this; and binding it initializeFinalForm.bind(self).

Example:
<some-form>

    ...

    <script>

        export default withFinalForm({
            manuallyInitializeFinalForm: true,

            // Simple async or sync usage
            async onMounted() {

                this.initialValues = await getStateFromSomewhere();

                if (specificCondition) {

                    this.validate = otherValidationFunction
                }

                this.initializeFinalForm();
            }

            // Nested lexical this
            onMounted() {

                // Reference component
                const self = this;

                getData().then((data) => {

                    getMoreData().then(data2 => {

                        self.initialValues = someData;

                        // Must pass component for cases where you cannot
                        // depend on lexical this
                        self.initializeFinalForm.apply(self);
                    })
                });
            }

            // External configuration
            onMounted() {

                const self = this;

                const callback = () => self.initializeFinalForm.apply(self);

                // Dynamically configure any final form configs before applyings
                dynamicallyConfigure(self, callback);
            }
        })
    </script>
</some-form>

Notes:

e.preventDefault() is called on submit unless explicitly specified otherwise. See this

Input fields can have an ignore attribute attached to them which will flag them to be skipped for registration by final form. For example:

<input type='hidden' name='csrf_token' value='abc123' ignore />
<input type='hidden' name='post_id' value='1' ignore />

withFinalForm(component)

Creates a final form wrapper for a component. Automatically unsubscribes and removes form when component unmounts. Configuration callbacks are all called bound to the riot component, so the lexical this will be the same as onMounted. The following configuration options are available:

ParamTypeDescription
component.formElementfunctionRequired function that returns the form element to bind to
component.onSubmitfunctionFinal Form submit function. Prevents defeault behavior. If undefined, default DOM behavior will occur.
component.initialValuesobjectFinal Form initialValues
component.validatefunctionForm validate function
component.onFormChangeonFormChangeFinal Form listener that passes form state
component.onFormMutatedonFormMutatedMutationObserver that listens for changes in the HTML form
component.formSubscriptionsobjectFinal Form subscriptions
component.formConfigobjectFinal Form configs
component.onFieldChangeonFieldChangeCallback ran when a field changes
component.fieldSubscriptionsobjectFinal Form field subscriptions
component.fieldConfigsobjectFinal Form field configs
component.manuallyInitializeFinalFormbooleanIn case you want to manually initialize final form after some async event. Read more about this flag.

onFormChange : function

Form change callback

ParamTypeDescription
formStateobjectfinal form state

onFormMutated : function

Mutation observer callback

ParamTypeDescription
formMutationOptionsobjectOptions to perform changes to final form state
<script>

    withFinalForm({

        // ...

        onFormMutated(formMutationOptions) {

            const {
                // Mutation observer callback
                mutationsList,
                observer,

                // Map of registrations (Map<HTMLElement, deregisterFunction()>)
                registrations,

                // Final form API
                form,

                // registerField(field: HTMLFormInputElement)
                registerField
            } = formMutationOptions;
            

            for (const mutations of mutationsList) {
                
                const {
                    addedNodes,
                    removedNodes
                } = mutation;

                for (const el of [...addedNodes]) {

                    if (/^(?:input|select|textarea)$/i.test(el.nodeName)) {

                        registerField(el);
                    }
                }

                for (const el of [...removedNodes]) {

                    if (registrations.has(el)) {

                        const unregister = registrations.get(el);
                        unregister();
                    }
                }
            }
        }

        // ...

    });
</script>

onFieldChange : function

Field change callback

ParamTypeDescription
fieldHTMLElementform field
fieldStateobjectfinal form field state
2.1.4

3 years ago

2.1.3

3 years ago

2.1.2

3 years ago

2.1.1

3 years ago

2.1.0-next.1

3 years ago

2.1.0

3 years ago

2.0.1

3 years ago

2.0.0

3 years ago

1.2.0

3 years ago

1.1.0

3 years ago

1.0.1

4 years ago

1.0.0

4 years ago