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

@atmina/formbuilder

v2.1.1

Published

A strongly-typed alternative API for React Hook Form.

Downloads

1,216

Readme

FormBuilder

Write composable and type-safe form components, including subforms and field level components. Powered by React Hook Form.

Installation

npm install @atmina/formbuilder
# or
yarn add @atmina/formbuilder

Usage

FormBuilder exposes a single hook useFormBuilder which is mostly compatible with useForm from react-hook-form. It contains an additional member, fields, which represents an alternative, object-oriented API on top of React Hook Form. Each field in the form data can be accessed as a property, including nested fields. The field can be called as a function to register an input. It also exposes RHF functions via field-level methods, e.g. $setValue. These methods are prefixed with $ to prevent potential conflicts with form data members.

import {useFormBuilder} from '@atmina/formbuilder';

interface Address {
    state: string;
    city: string;
    street: string;
    zip: string;
}

const App = () => {
    const {fields, handleSubmit} = useFormBuilder<{
        name: string;
        address: Address;
    }>();

    const handleFormSubmit = handleSubmit((data) => {
        console.log(data);
    });

    return (
        <form onSubmit={handleFormSubmit}>
            <input {...fields.name()} />
            <input {...fields.address.city()} />
            {/* etc. */}
        </form>
    );
};

Fields

You can create components that encapsulate a single (typed) field by accepting a FormBuilder<T> prop where T is the type of the field. We like to call this on or field, but you are free to name it however you like.

import {FC} from 'react';

const TextField: FC<{on: FormBuilder<string>; label: string}> = ({
    on: field,
}) => {
    return (
        <div>
            <label>
                <span>{label}</span>
                <input type='text' {...field()} />
            </label>
            <button
                type='button'
                onClick={() => field.$setValue(getRandomName())}
            >
                Randomize
            </button>
        </div>
    );
};

The field component would be used like this:

-   <input type="text" {...field.name()} />
+   <TextField label="State" on={fields.name} />

This ensures that the TextField component can only accept fields typed as strings, resulting in a type error otherwise.

Subforms

You can create components which encapsulate a group of related fields, such as an address. Subforms are useful for composition, letting you piece together complex data structures and adding a lot of reusability to your forms.

import {FC} from 'react';

const AddressSubform: FC<{field: FormBuilder<Address>}> = ({field}) => {
    return (
        <div>
            <TextField label='State' field={field.state} />
            <TextField label='City' field={field.city} />
            {/* etc. */}
        </div>
    );
};

Field arrays

Fields which are typed as arrays provide a $useFieldArray() hook which can be used to map over the contents, as well as mutate them using operations such as append, insert, move and remove.

The fields returned by $useFieldArray are themselves FormBuilders that can be registered on inputs or passed to other Subform components.

import { FC } from "react";

const AddressesSubform: FC<{field: FormBuilder<Person[]>}> = ({field}) => {
    const {fields, append} = field.$useFieldArray();
    const add = () => {
        append({state: '', city: '', /* etc. */});
    }
    return <div>
        {fields.map(f => <AddressSubForm key={f.$key} field={f} />)}
        <button onClick={add}>Add new address</button>
    <div>
}

The $key contains a unique id for the array item and must be passed as the key when rendering the list.

Note: Field arrays are intended for use with arrays of objects. When dealing with arrays of primitives, you can either wrap the primitive in an object, or use a controller ($useController) to implement your own array logic.

For more information, see the React Hook Form docs on useFieldArray.

Discriminated unions

In case of a form that contains fields with object unions, the $discriminate() function may be used to narrow the type using a specific member like this:

import {FC} from 'react';

type DiscriminatedForm =
    | {__typename: 'foo'; foo: string}
    | {__typename: 'bar'; bar: number};

const DiscriminatedSubform: FC<{field: FormBuilder<DiscriminatedForm>}> = ({
    field,
}) => {
    const [typename, narrowed] = field.$discriminate('__typename');

    switch (typename) {
      case 'foo':
        return <input {...narrowed.foo()} />;
      case 'bar':
        // ...
    }
};

This returns the current value of the discriminator as well as a FormBuilder that is automatically narrowed when the discriminator is checked, for example in a switch or if block.

Compatibility with useForm

Currently, useFormBuilder is almost compatible with useForm. This means you get the entire bag of tools provided by useForm, in addition to the fields API. This provides an escape hatch for use cases not yet covered by useFormBuilder. However, future versions of the library may see us diverging further from useForm in an effort to streamline this API and increase its type-safety.

License

MIT