npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

riot-final-form

v2.1.4

Published

Riot HOC for using Final Form

Downloads

6

Readme

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:

| Param | Type | Description | | ------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | component.formElement | function | Required function that returns the form element to bind to | | component.onSubmit | function | Final Form submit function. Prevents defeault behavior. If undefined, default DOM behavior will occur. | | component.initialValues | object | Final Form initialValues | | component.validate | function | Form validate function | | component.onFormChange | onFormChange | Final Form listener that passes form state | | component.onFormMutated | onFormMutated | MutationObserver that listens for changes in the HTML form | | component.formSubscriptions | object | Final Form subscriptions | | component.formConfig | object | Final Form configs | | component.onFieldChange | onFieldChange | Callback ran when a field changes | | component.fieldSubscriptions | object | Final Form field subscriptions | | component.fieldConfigs | object | Final Form field configs | | component.manuallyInitializeFinalForm | boolean | In case you want to manually initialize final form after some async event. Read more about this flag. |


onFormChange : function

Form change callback

| Param | Type | Description | | --------- | -------- | ---------------- | | formState | object | final form state |


onFormMutated : function

Mutation observer callback

| Param | Type | Description | | ------------------- | -------- | ---------------------------------------------- | | formMutationOptions | object | Options 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

| Param | Type | Description | | ---------- | ------------- | ---------------------- | | field | HTMLElement | form field | | fieldState | object | final form field state |