npm package: @mlaopane/formhook
Description: Handle forms in React using hooks
Author: Mickaël LAO-PANE
Latest version: 1.9.12

# FormHook

[![pipeline status](https://gitlab.com/mlaopane/formhook/badges/master/pipeline.svg)](https://gitlab.com/mlaopane/formhook/commits/master)
[![coverage report](https://gitlab.com/mlaopane/formhook/badges/master/coverage.svg)](https://gitlab.com/mlaopane/formhook/commits/master)

## Installation

```bash
yarn add @mlaopane/formhook
```

OR

```bash
npm i --save @mlaopane/formhook
```

Checkout the [examples](#examples) for usage.

## What is FormHook

[**FormHook**](https://www.npmjs.com/package/@mlaopane/formhook) is a library allowing developers to easily create and handle **forms** with [React](https://fr.reactjs.org/).

## Why should I use FormHook

**_Maintaining a form state is cumbersome._**

Traditionally you need to :

-   Update the state when an input changes
-   Process validation based on events (change, blur, submit)

**FormHook** will handle these tasks for you.

## How it works

The library exposes a few components and functions to help you create cleaner forms.

Under the hood, it uses :

-   React [context](https://reactjs.org/docs/context.html)
-   React [hooks](https://fr.reactjs.org/docs/hooks-intro.html) (introduced in React 16.8)

By using the `<Form>...</Form>` component, a context is internally created and maintained.

Any child inside the Form will have access to the context.

## Examples

Here is a simple example :

```jsx
// LoginForm.jsx

import React from 'react';
import { Form, Input, Button } from '@mlaopane/formhook';
// Your custom validator (defined below)
import emailValidator from './emailValidator';

export default function SignInForm() {
    const initialValues = {
        email: '',
        password: '',
    };

    const handleSubmit = ({ form, dispatch }) => {
        /**
         * Do whatever you need with the values
         * like sending them to an API
         */
        console.log(form.values);

        /**
         * Set form.isSubmitting to `false`
         * The provided Button component is automatically disabled
         * when the form is being submitted
         */
        dispatch({ type: 'END_SUBMIT' });
    };

    // The state of the form is handled by the Form component
    return (
        <Form initialValues={initialValues} onSubmit={handleSubmit}>
            <Input name='email' validate={emailValidator.validate} />
            <Input name='password' />
            <Button>Sign in</Button>
        </Form>
    );
}
```

```javascript
// emailValidator.js

/**
 * You need to return an empty string if there is no error
 */
export const validate = async ({ field }) => {
    if (!field.value) {
        return 'E-mail is mandatory';
    }
    if (!field.value.includes('@')) {
        return 'Invalid e-mail';
    }
    return '';
};
```

If you need a more advanced example, you can checkout the `examples` directory of the [formhook's repository](https://gitlab.com/mlaopane/formhook).

## FormHook API

The public components/functions can be retrieved from the global module like this :

```jsx
// Example
import { Form, FormContext, useInput } from '@mlaopane/formhook';
```

## Main components

### Form

The `Form` component is mandatory to leverage the power of this library.
Pass in the `initialValues` and the `onSubmit` function handler.

### Input

The `Input` component automatically dispatches new values on every change.  
Just pass in the `name` as a prop.

It allows validation too by passing async functions (cf. [Validation](#validation)).

To define a validation function, you can declare an async function (cf. [async/await](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Instructions/async_function)).  
This function must return an error string or an empty string if there is no error.

### Spy

You may need to retrieve the form state to use it outside of the form.
You could implement your own component using the exposed `FormContext` or more simple, use the provided `Spy`.

```jsx
import React from 'react';
import { Form, Spy } from '@mlaopane/formhook';

export default function MyComponent() {
    const initialValues = { fieldName: '' };

    function showMeWhatYouGot({ form }) {
        console.log(form);
    }

    return (
        <Form initialValues={initialValues}>
            <Spy hookContext={showMeWhatYouGot} />
        </Form>
    );
}
```

### FormContext

The `FormContext` can be imported and used by any child component of the `Form`.

It exposes an tuple with the form state and the dispatch function.

```jsx
import React from 'react';
import { FormContext } from '@mlaopane/formhook';

export default function MyComponent() {
    const [form, dispatch] = React.useContext(FormContext);
    // ...
    return <div>{JSON.stringify(form)}</div>;
}
```

The form state looks like this :

```json
{
    "errors": {
        "fieldName": ""
    },
    "focused": "",
    "isValid": true,
    "isSubmitting": false,
    "options": {
        "fieldName": []
    },
    "touched": {
        "fieldName": false
    },
    "validators": {
        "fieldName": {}
    },
    "values": {
        "fieldName": ""
    }
}
```

## Validation

The field components accept validation functions as props :

-   `validateOnChange` is called when the input value changes.

-   `validateOnBlur` is called when the input loses the focus.

-   `validateOnSubmit` is called on form submission.

-   `validate` serves as a fallback.

In summary : If you don't need custom validation function for every event, just use `validate`.