npm package: @paros-ui/react-forms
Description: Yet another React form library
Author: alex.jenks@paros.id
Latest version: 3.1.6

# @paros-ui/react-forms

On the web, forms exist everywhere. Here is yet another way to write React forms, vaguely inspired by [mobx](https://www.npmjs.com/package/mobx) and [redux-form](https://www.npmjs.com/package/redux-form) / [react-final-form](https://www.npmjs.com/package/react-final-form)

[![npm version](https://badge.fury.io/js/%40paros-ui%2Freact-forms.svg)](https://badge.fury.io/js/%40paros-ui%2Freact-forms)

## Building a Form

The first step is to initialize your form object. It will be in charge of state, validation, and event handling.

Once initialized, you can render the Form component and any number of Fields. Their name is specified by accessing properties of the `Fields` object

```jsx
import React from "react";
import ReactForm from "@paros-ui/react-forms";

import LoginSchema from "./schemas/login";

class TestForm extends React.Component {
    form = new ReactForm({
        initialValues: {
            rememberMe: true
        }
    });

    onSubmit = data => {
        console.log(data); //{ "username": "...", "password": "...", "rememberMe": true }
    };

    render() {
        let { Form, Fields } = this.form;

        return (
            <Form onSubmit={this.onSubmit}>
                {/* This is a field called "username" */}
                <Fields.username component="input" type="email" placeholder="Email address" />
                {/* This is a field called "password" */}
                <Fields.password component="input" type="password" placeholder="Password" />

                {/* This is a field called "rememberMe" */}
                <Fields.rememberMe template="checkbox" component="input" />
                <label for="rememberMe">Remember Me</label>

                <button type="submit">Login</button>
            </Form>
        );
    }
}
```

The `Form` component will automatically intercept the submit event, perform validation (if configured), and call your `onSubmit` with the validated form data. If you use joi, it will provide the sanitized value from `schema.validate()`, otherwise it will be the raw form state.

`Fields` is a JavaScript proxy, like a dynamic object. When you reference a property of it, it'll generate and cache a wrapper component linked to that form, where the name of that field is the name of the property you are accessing. In the above example, it is caching three components and initializing the form state with three values, `username`, `password` and `rememberMe`. On blur, it will validate that field. On change, will reset the validation status for that field. It will also pass `onBlur` and `onChange` events through after it handles them internally.

### Using a React Hook

If you prefer to use hooks, a `useForm` hook is available. It is just a wrapper around `useState` to manage a reference to ReactForm, making the hook functionally identical. All documentation will demonstrate with the ReactForm class for consistency, but can be implemented the same way using the `useForm` hook.

```jsx
import React from "react";
import useForm from "@paros-ui/react-forms/hooks/useForm";

const onSubmit = (values, form) => {
    // The second parameter gives you access to the ReactForm object
    console.log(data); //{ "username": "...", "password": "...", "rememberMe": true }
};

let form = {
    initialValues: {
        rememberMe: true
    }
};

function Component(props) {
    let { Form, Fields } = useForm(form);

    return (
        <Form onSubmit={onSubmit}>
            <Fields.username component="input" type="email" placeholder="Email address" />
            <Fields.password component="input" type="password" placeholder="Password" />

            <Fields.rememberMe template="checkbox" component="input" />
            <label for="rememberMe">Remember Me</label>

            <button type="submit">Login</button>
        </Form>
    );
}
```

## Default Values

Initial values can be passed into the `ReactForm` options object, or the `<Form />` component.

The `<Form />` component has higher priority, so if both are specified, it will defer to that.

```jsx
let { Form } = new ReactForm({
    initialValues: {
        rememberMe: true
    }
});

<Form initialValues={{ rememberMe: false }}>
    ...
</Form>
```

## Validation
Validation is supported via both [joi](https://www.npmjs.com/package/joi) object schemas and custom validator functions. The validator can be passed into the `ReactForm` options object, or like with the `initialValues` it can be passed as a prop to `<Form />`:

```jsx
import joi from "joi";

const schema = joi.object({
    username: joi.string().email({ tlds: { allow: false } }).required(),
    password: joi.string().min(6).max(42).required()
});

let { Form } = new ReactForm({
    validation: schema
});

<Form validation={schema}>
    ...
</Form>
```

Alternatively, for fine-grain control over the validation process, you can pass a function as the `validation` prop:

```jsx
let form = new ReactForm({
    validation: (data, errors) => {
        if(data.password !== "secret")
            errors.password = "Invalid password";
    }
});
```

The validation function can even be asynchronous!

```jsx
let form = new ReactForm({
    validation: async(data, errors) => {
        if(!(await checkToken(data.token)))
            errors.token = "Invalid token";
    }
});
```

### Validation on Blur

When a joi schema is used as validation or is not explicitly configured, when a field blur event fires, it will automatically validate the field. If a function validator is used, or `validateOnBlur` disabled either at the field or form level, it will disable this functionality.

```jsx
let { Fields } = new ReactForm({
    validateOnBlur: false // Disable at the form level
});

// Enable for this specific field
<Fields.username component="input" type="text" placeholder="Username" validateOnBlur />
```

## Simple Inputs

For most projects, you don't use a super complex text input for usernames, passwords, checkboxes, etc.

For this reason, there are a number of templates available. They will autofill certain properties when hooking into the component so you can write less code. Simply provide a `template` property with the desired template name, and it will take care of the rest.

### Checkboxes
```jsx
const Checkbox = ({ label, id, ...props }) => (
    <div className="form-group">
        <input type="checkbox" id={id} {...props} />
        <label for={id}>{label}</label>
    </div>
);

// Without using the template.
<Fields.rememberMe component={Checkbox} valueProp="checked" valueGetter="target.checked" label="Remember me" />

// With the `checkbox` template
<Fields.rememberMe template="checkbox" component={Checkbox} label="Remember me" />
```

### Adding Templates

When you want to create reusable form components, you don't need to import it everywhere. Instead, you can extend the form system's built-in templating.

```jsx
import { addTemplates } from "@paros-ui/react-forms/templates";

addTemplates({
    "password": ({ output, template, templateProps }) => {
        if(!output.Component) {
            output.Component = "input";
        }

        templateProps.type = "password";
        template.forbidNull = true;
    }
});

<Fields.fieldName template="password" placeholder="Enter a password" />
```

## Complex Inputs

Most of the time, the above will cover most use cases. However, sometimes your inputs are more complex than standard HTML `input`s, and/or have more complex data. For this, we can use `valueProp`, `valueGetter`, and `template` to customize exactly how the form interfaces with your inputs.

### `valueProp` (default: `"value"`)
This will determine how the form will set the `value` property on the component. For checkboxes, `valueProp` needs to be `"checked"`, because this is the property React expects for setting the value.

### `valueGetter` (default: `"target.value"`)
This determines how to extract the value from the `onChange` event. For checkboxes, `valueGetter` needs to be `"target.checked"`. `valueGetter` can be a string or a synchronous function.

```jsx
<Fields.fieldName component="input" type="checkbox" valueProp="checked" valueGetter="target.checked" />
<Fields.fieldName component="input" type="checkbox" valueProp="checked" valueGetter={e => e.target.checked} />
```

## ArrayFieldAdapter

Sometimes, your form utilizes an array of malleable data. For this, the `ArrayFieldAdapter` was created.

Give it a `keyProp` for your data, and a body to render, and it will automatically track changes without updating every item. Very performant!

The function you specify can have three parameters, `item`, `props` and `index`.

`props` has the `value`, `error`, `onChange`, etc. for that specific item. This gives you complete control over how you render each item.

```jsx

const ComplexComponent = ({
    item, value, onChange
}) => (
    <div className="form-group">
        <label>{item.name}</label>
        <input type="phone"
            className="form-control"
            placeholder="Enter Phone Number"
            value={value}
            onChange={onChange} />
    </div>
);

<Fields.phoneNumbers template="array" keyProp="id">
    {(item, props) => (
        <ComplexComponent item={item} {...props}>
    )}
</Fields.phoneNumbers>

```

## Complex Data Structures

Forms are rarely always a flat object. Utilities like the [`FormObject`](./src/FormObject.d.ts) were created just for this.

### FormObject

Nesting a form component like a Field inside of a `FormObject` will change the path to its value.

Take this data structure as an example:
```json
{
    "email": "",
    "creditCard": {
        "number": "",
        "exp": "",
        "cvv": ""
    }
}
```

You could implement the form with no additional effort by utilizing a FormObject wrapper:

```jsx

<Fields.email template="text" type="email" />

<FormObject field="creditCard">
    <Fields.number template="number" format="####-####-####-####" />
    <Fields.exp template="number" format="##/##" />
    <Fields.cvv template="number" format="###" />
</FormObject>

```

## Monitoring, Field Dependencies and Reactions

Sometimes, complex form interactions between fields are necessary. Because of this, some advanced components and utilities have been implemented.

## FieldWatcher

The most simple monitor is a [`FieldWatcher`](./src/FieldWatcher.d.ts). Give it a `field` or `fields` prop and it'll render a child function when those field(s) change. The parameter given to the function is an object with the keys being each watched field.

```jsx
import ReactForm from "@paros-ui/react-forms";

let { Form, Fields, FieldWatcher } = new ReactForm({ ... });

<FieldWatcher field="testFieldName">
    {({ testFieldName }) => (
        // Your JSX here
    )}
</FieldWatcher>

<FieldWatcher fields={[ "fieldA", "fieldB" ]}>
    {({ fieldA, fieldB }) => (
        // Your JSX here
    )}
</FieldWatcher>
```


### Monitors

Fields sometimes need to reference values from other fields in the same form. To accomplish this, simply add a `monitors` prop to a field, and a prop will be injected with its value. Additionally, the component will update when the monitored values change. `monitors` behaves like both `field` and `fields` from FieldWatcher, so you can specify either an array or a single field.

```jsx

const EndDate = ({ startDate, value, onChange }) => (
    <DatePicker minDate={startDate} value={value} onChange={onChange} />
);

const OptionsDropdown = ({ startDate, endDate, ...props }) => (
    <AvailableOptionsDropdown startDate={startDate} endDate={endDate} {...props} />
);

<Fields.startDate template="datepicker" />
<Fields.endDate template="datepicker"
    monitors="startDate"
    component={EndDate} />
<Fields.options component={OptionsDropdnw} monitors={[ "startDate", "endDate" ]}>

```

### `fieldRequires` and `fieldExcludedBy`

Sometimes there is a hard relationship between two fields. One requires another be populated, or one may entirely disable another. These props for fields enable such relationships.

`fieldRequires` will automatically disabled the inner component if the specified field **is not** populated.

`fieldExcludedBy` will automatically disabled the inner component if the specified field **is** populated.

## Updating Form State

Using functions built into the form object, you can update and fetch information about the form at any time. Performing updates will trigger updates in affected fields.

### Get/Set Values
```jsx
let form = new ReactForm({
    initialValues: {
        rememberMe: false
    }
});

// Fetching a single value
let rememberMe = form.value("rememberMe"); // false

// Fetching multiple values
let [ username, password ] = form.values("username", "password");

// Fetching all values
let { username, password } = form.values();

// Setting a single value
form.value("rememberMe", true);

// Setting multiple values
form.values({
    rememberMe: true
});
```

### Get/Set Errors
```jsx
let form = new ReactForm({
    initialValues: {
        rememberMe: false
    }
});

// Fetching a single error
let rememberMe = form.error("rememberMe"); // null

// Fetching multiple errors
let [ username, password ] = form.errors("username", "password");

// Fetching all errors
let { username, password } = form.errors();

// Setting a single error
form.error("rememberMe", "Form error");

// Setting multiple errors
form.errors({
    rememberMe: "Form error"
});
```

### Get Form State

```jsx
let form = new ReactForm({
    initialValues: {
        rememberMe: false
    }
});

form.value("rememberMe", true);

// Get all values
let values = form.values();
// Get all errors
let errors = form.errors();

// Or get all errors and values together
let { errors, values } = form.getState();
console.log(values.rememberMe, errors.rememberMe); // true, undefined
```

### Reset a Form to Initial Values
```jsx
let form = new ReactForm({
    initialValues: {
        rememberMe: false
    }
});

form.value("rememberMe", true);
form.resetForm(); //Resets both values and errors
form.resetValues(); //Resets values
form.resetErrors(); //Resets errors

let value = form.value("rememberMe"); // false
```

### Reset Form Errors
```jsx
let form = new ReactForm({
    initialValues: {
        rememberMe: false
    }
});
```

### (UNSAFE) Replacing Values / Errors

If you want to force-set values or errors, and not update the form in any way, it can be achieved with the following utility functions. It is not recommended to use.

```jsx
let form = new ReactForm({
    initialValues: {
        username: "test_user",
        password: "Passw0rd!"
    }
});

form.replaceValues({
    username: "",
    password: ""
});

form.replaceErrors({
    username: "Invalid username",
    password: "Invalid password"
});
```

If you pass `true` as a second parameter to `replaceValues`, it will also set the `initialValues` to the specified object

### Manually Triggering Validation and Updating Components

The form object provides three utility functions for updating components or triggering validation.

```jsx
let form = new ReactForm({
    validation: (values, errors) => {
        if(!values.username)
            errors.username = "Username is required";
    }
});

let { Form, Fields } = form;

<Form>
    <Fields.username component="input" type="text" placeholder="Username" />
</Form>

form.validate(); // Will validate the entire form
form.validate("username"); // Will activate the validator and update the component
form.change("username"); // Will trigger the general change and update events, and update the field
form.update("username"); // Will trigger the field-specific change event, updating the field
```

### Changing the Validator at any Time

The validator can be changed dynamically by accessing the `validation` property of the form object.

```jsx
let form = new ReactForm({
    validation: () => null
});

// Future validation will use the new function / schema
form.validation = joi.object();
```

## Internal Events

ReactForm extends a facsimile of the Node.js EventEmitter, so you can subscribe to any internal event or manually emit events with the familiar `.on()`, `.off()`, `.emit()`, etc. functions.

Additionally, when initializing `ReactForm`, you can optionally specify a submit and/or change handler. You can do the same when rendering the `<Form />` component:

```jsx
let onChange = ({ field, value }) => {};
let onSubmit = values => {};

let { Form } = new ReactForm({
    onChange: onChange,
    onSubmit: onSubmit
});

<Form onChange={onChange} onSubmit={onSubmit}>
    ...
</Form>
```

### "`change`"

Change is fired when any field's value or error changes.

```jsx
let form = new ReactForm();

// Using helper function
form.onChange(({ field, value }) => {
    console.log(`Field "${field}" has value ${value}`);
});

// Using generic event subscription
form.on("change", ({ field, value }) => {
    console.log(`Field "${field}" has value ${value}`);
});
```

### "`update`"

Update is fired when the form updates, from validation to field value change to form submit.

```jsx
let form = new ReactForm();

// Using generic event subscription
form.on("update", ({ errors, values }) => {
    ...
});
```

### "`submit`"

Fired when the form passes validation and is ready to be processed externally. This event is used to fire any `onSubmit` provided.

```jsx
let form = new ReactForm();

// Using helper function
form.onSubmit(values => {
    // ...
});

// Using generic event subscription
form.on("submit", values => {
    // ...
});
```

### "`errors`"

Fired when the form submits but fails validation.

```jsx
let form = new ReactForm();

// Using generic event subscription
form.on("errors", errors => {
    // ...
});
```

### "`change-{field name}`"

Fired immediately after the generic "change" event, but this allows you to listen to a specific field's updates.

```jsx
let form = new ReactForm();

// Using generic event subscription
form.on("change-username", (value, error) => {
    // ...
});
```

## `<Fields />` Utilities and Props

The `<Fields />` component will pass all props it receives to its `component`, including refs, but there are a few that will dictate behavior and not be passed through.

### `component`

This determines how to actually render the field.

### `template`

This will let you choose a template to be used internally by the field component builder to autofill some properties. The following templates come built-in, and can be extended/overridden:

* `"component"` - Only adds a default for the `valueGetter`, using the direct onChange value.
* `"text"` - Defaults to `<input type="text" />` and forbids null values, forcing the form value to at least be `""`
* `"textarea"` - Same as `text` template, just uses `<textarea />` instead.
* `"checkbox"` - Defaults to `<input type="checkbox">`, sets default value to false, and sets defaults for `valueGetter` / `valueProp`
* `"select"` - Sets some convenient defaults for a `react-select` integration. Requires extending to provide the default component.
* `"array"` - Uses the [`ArrayFieldAdapter`](./src/ArrayFieldAdapter.d.ts)

### `defaultValue`

Behaves similarly to `initialValues` when initializing the form, but this allows you to set it on a per-field basis instead of specifying all at once with `initialValues`. Has priority over form-level `initialValues` setting.

### `validateOnBlur`

Behaves similarly to `validateOnBlur` when initializing the form, but this allows you to toggle validation on a per-field basis. Has priority over form-level setting.

### `name`

Since all fields are controlled components, you shouldn't need to care about the name property, and it'll by default be filtered out. If you absolutely *must* set it, use the `componentProps` property defined below.

### `componentProps`

If for some reason you want to use some of these property names for the inner component, you can specify them in a `componentProps` object.

## `<Form />` Utilities and Props

`<Form />` will pass refs to the internal `<form />` component.

## ReactForm Utilities and Props

### `debugUpdates` (default: `false`)

This will log any `update` events to the console.

### `debugChanges` (default: `false`)

This will log any `change` events to the console.

### `onError`

This optional property expects a function, and will subscribe to the `errors` event that fires when a form submits but fails validation.

### `onUpdate`

This optional property expects a function, and will subscribe to the `update` event that fires when a form changes state at all.

### `name` (default: `"form"`)

Currently unused except for `debugUpdates` and `debugChanges` to prefix update/change logging.