react-forminator
TypeScript icon, indicating that this package has built-in type declarations

0.2.5 • Public • Published

Forminator

The only form library you need for React

⚠ WIP

Installation

Surprisingly:
npm i react-forminator

Usage

Forminator does its best to be unopinionated about how should one handle forms, thus keeps its core API as low-level as possible.
But of course there are lot of common scenarios and Forminator also gives higher-order helpers to achieve them easily.

Step one

The core concept of Forminator is the form description.
It looks like this

import { FormDescriptor } from 'react-forminator';

const formDescriptor: FormDescriptor = {
    fields: {
        requiredField:{
            value: '',
            validate: [
                required('This field is required.')
            ]
        }, 
        optionalField: '',
    },
    onSubmit(fieldValues) {
        sendThemSomeWhere(fieldValues);
    }
};

Step two

Next, form object needs to be created given the descriptor.
In plain JS, you would do:

import { Forminator } from 'react-forminator';

const form = new Forminator(descriptor);

You can also use useForm hook to construct the form instance in the component itself so you can have more control what happens when the form is submitted (e.g. change a local state)

import { useForm, FormDescriptor, SubmitFn } from 'react-forminator';

const formDescriptor: FormDescriptor = {...};

const CoolestForm: FunctionComponent = () => {
    const [submitted, setSubmitted] = useState(false);

    const onSubmit = useCallback<SubmitFn>(async values => {
        await sendThemSomeWhere(fieldValues);
        setSubmitted(true);
    }, []);

    const form = useForm(formDescriptor, onSubmit);

    return (
        // ... Read further to see what should go here
    );
}

Step three

Forminator forms should use the Form component, and pass the form object (Forminator instance) as a prop.

import { Forminator, FormDescriptor, Form } from 'react-forminator';

const formDescriptor: FormDescriptor = {...};
const form = new Forminator(formDescriptor);

const CoolestForm: FunctionComponent = () => {
    return (
        <Form form={form}>
            {/* Fields come next */}
        </Form>
    );
}

Step four

The final step to have a working form is to use Field for each field in the form

It has two low-level APIs.
First one is to give a child function which will receive value, setValue, onBlur, error as params, and should return a ReactNode.

And the second one is to use the FieldContext and extract those params from there.

<Form form={form}>
    <Field name="fieldNameAsInDescriptor">
        {(value, setValue, onBlur, error) =>
            <label>
                <input
                    type="text"
                    value={value}
                    onChange={evt => setValue(evt.target.value)}
                    onBlur={onBlur}
                />
                <span>{error?.message}</span>
            </label>
        }
    </Field>
</Form>

Generally you would wrap your inputs into a component which will "look better".

form-field.tsx

export const FormField: FunctionComponent<Props> = props => {
    const { name, label, children } = props;

    return (
        <div className={classes('form-field', [!!error, 'has-error'])}>
            <Field name={name}>
                <label>
                    {label}
                    {children}
                </label>
                <FieldContext.Consumer>
                    {({error}) => <div className="error">{error?.message}</div> }
                </FieldContext.Consumer>
            </Field>
        </div>
    );
};
interface Props extends InputHTMLAttributes<HTMLInputElement> {
    someProp: string;
}

export const Input: FunctionComponent<Props> = props => {
    const { someProp, ...rest } = props;
    const fieldContext = useContext(FieldContext);
    const { value, setValue, onBlur } = fieldContext;

    const onChange = (evt: ChangeEvent<HTMLInputElement) => {
        setValue(evt.target.value);
    };

    return (
        <input
            value={value}
            onChange={onChange}
            onBlur={onBlur}
            {...rest}
        />
    );
}

And use them like this:

export const CoolestForm: FunctionComponent = () => {
    return (
        <Form form={form}>
            <FormField name="email" label="Email">
                <Input placeholder="email@email.com" />
            </FormField>
            <FormField name="firstName" label="First name">
                <Input placeholder="John" />
            </FormField>
        </Form>
    );
};

Why another form library with a similar API?

...

Validation

...

Some patterns

Submit form from outside

Each form instance has a unique id, so you can just create button element anywhere and give the form's id to the form attribute of the button.

<button type="submit" form={form.id} />

Or if you want to submit the form programmatically, you can just

form.submit()

Package Sidebar

Install

npm i react-forminator

Weekly Downloads

85

Version

0.2.5

License

MIT

Unpacked Size

42.1 kB

Total Files

28

Last publish

Collaborators

  • nairinarinyan
  • martin-khachatryan