Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/balazsorban44/use-form

A React hook 🎣 for easy form handling
https://github.com/balazsorban44/use-form

custom-hook form form-validation form-validation-react react react-hook

Last synced: 15 days ago
JSON representation

A React hook 🎣 for easy form handling

Awesome Lists containing this project

README

        


another-use-form-hook

A React hook 🎣 for easy form handling

---

[![Build Status][build-badge]][build] [![Code Coverage][coverage-badge]][coverage] [![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends] [![Total downloads][total-downloads]][total-downloads] [![MIT License][license-badge]][license] [![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc]

[![Watch on GitHub][github-watch-badge]][github-watch] [![Star on GitHub][github-star-badge]][github-star]

---

## Table of Contents

- [Table of Contents](#table-of-contents)
- [Installation](#installation)
- [Usage](#usage)
- [Documentation](#documentation)
- [useForm](#useform)
- [UseFormParams](#useformparams)
- [InitialState](#initialstate)
- [Validators](#validators)
- [Validations](#validations)
- [SubmitCallback](#submitcallback)
- [NotifyCallback](#notifycallback)
- [UseFormReturn](#useformreturn)
- [FieldValuesAndErrors](#fieldvaluesanderrors)
- [ChangeHandler](#changehandler)
- [SubmitHandler](#submithandler)
- [InputPropGenerators](#inputpropgenerators)
- [InputTypeOptions](#inputtypeoptions)
- [InputPropGeneratorsReturn](#inputpropgeneratorsreturn)
- [FormProvider](#formprovider)
- [FormProviderProps](#formproviderprops)
- [getForms](#getforms)
- [Forms](#forms)
- [LICENSE](#license)

## Installation
This should be installed as one of your project `dependencies`:

```
yarn add another-use-form-hook
```
or
```
npm install --save another-use-form-hook
```

> NOTE: `another-use-form-hook` only works with [**react >=16.8**][react-hooks], since it is a hook.

## Usage

This hook is intended to give a full solution for handling forms. From interdependent field value validations (meaning if a field value is dependent on other field value), to submitting the form, and providing information about when the UI should be unresponsive (loading of some kind of async-like operation), in addition to notification "hooks" to be able to inform the users the most efficient way.

To retrieve props for an input field, you have the following options:
1. Using the `form.inputs.{inputType}('name')` input prop generator function (`inputTypes` is one of [these][input-types-mdn])
2. Using `form.fields.{name}.{value|error}` and `form.handleChange` functions

> NOTE: The example below is available live at [CodeSandbox][codesandbox-example]
>
Let's see a complex example to understand how it works:

```jsx
import React from "react";
import ReactDOM from "react-dom";
import useForm from "another-use-form-hook";
import "./styles.css";
/**
* NOTE: We are using date-fns for this example,
* but it is absolutly not a requirement.
*/
import { addDays, isAfter, differenceInDays, parseISO } from "date-fns";
const isValidEmail = email => /\w*@\w*\.\w*/.test(email); // Do better 💩

const TODAY = new Date();

const App = () => {
const form = useForm({
initialState: {
email: "",
arrival: TODAY,
departure: TODAY
},
validators: (fields, isSubmitting) => ({
// if not submitting, don't throw errors for invalid e-mail, like an empty field
email: isSubmitting
? isValidEmail(fields.email)
: typeof fields.email === "string",
// earliest arrival must be tomorrow
arrival: isAfter(parseISO(fields.arrival), TODAY),
// earliest departure must be after tomorrow
departure: isAfter(addDays(parseISO(fields.departure), 1), TODAY),
// departure must be at least a day after arrival
minOneNight: isSubmitting
? differenceInDays(
parseISO(fields.departure),
parseISO(fields.arrival)
) >= 1
: true
}),
onNotify: (type, reason) => {
// you can use type and reason to send specific notifications to the user
switch (type) {
case "submitError":
console.error("Form could not be submitted: ", reason);
break;
case "submitSuccess":
console.info("Form has been submitted.", reason);
break;
case "validationErrors":
console.warn(
"The following problems occurred while validating: ",
reason
);
break;
default:
break;
}
},
onSubmit: async ({ fields, setLoading, notify }) => {
try {
setLoading(true);
// submitting the form, eg.: fetch("path/to/my/submit")
const response = await new Promise(resolve => {
setTimeout(() => {
console.log("Submitting: ", fields);
resolve(fields);
}, 1000);
});
notify("submitSuccess", response);
} catch (error) {
notify("submitError", error.message);
} finally {
setLoading(false);
}
}
});

return (

{/* option 1 (control all the props with a one-liner)*/}

Option 1


{form.fields.email.error ? "Invalid" : ""} email


{form.fields.arrival.error ? "Invalid" : ""} arrival

form.handleChange(e, ["minOneNight"])}
/>

{/* option 2 specify id, type, name, value props manually*/}

Option 2

{form.fields.arrival.error ? "Invalid" : ""} arrival

form.handleChange(e, ["minOneNight"])}
/>


{form.fields.departure.error ? "Invalid" : ""} departure

form.handleChange(e, ["minOneNight"])}
/>

{/* also from option 1 */}

Submit


);
};

ReactDOM.render(, document.querySelector("#root"));
```

## Documentation

### useForm

```ts
useForm(useFormParams: UserFormParams): UseForm
```

#### UseFormParams

| name | type | description |
| :----------- | :--------- | -------------------------------------------------------------------: |
| name | `string` | Refer to one of the forms in [formProviderProps](#formproviderprops) |
| initialState | `object` | See [InitialState](#initialstate) |
| validators | `function` | See [Validators](#validators) |
| onSubmit | `function` | See [SubmitCallback](#submitcallback) |
| onNotify | `function` | See [NotifyCallback](#notifycallback) |

##### InitialState
An object containing the default value of every field in a form.
> Example:
```js
useForm({
initialState: {
email: "",
name: "",
address: "",
age: 0
}
})
```

If `name` is defined, you can refer to `initialStates.{name}` in [formProviderProps](#formproviderprops).

> Example:
```jsx
//...

//...
const form = useForm({name: "login"})
console.log(form.fields.email.value) // [email protected]
```

##### Validators
This function is invoked before [onChange](#changecallback) and [onSubmit](#submitcallback). The former only runs the validations for the changed fields, while the latter runs it on the whole form. For convenience, it is also returned from `useForm`.
```ts
function validators(fields: object, isSubmitting: boolean): Validations
```

| name | type | description |
| :---------- | :-------- | ---------------------------------------------------------------: |
| fields | `object` | An object with the same shape as [initialState](#initialstate) |
| submitting | `boolean` | Set to `true`, when called before [handleSubmit](#submithandler) |
| validations | `object` | See [Validations](#validations) |

###### Validations
An object containing `boolean` expressions. Each input field must have at least a corresponding property in this object, but you can define custom ones as well.
> Example:
```js
{
email: submitting
? isValidEmail(fields.email)
: typeof fields.email === "string"
}
```
You can also look at the [live example](#usage).

##### SubmitCallback
Invoked when [handleSubmit](#submithandler) is called and there were no validation issues.

```ts
function onSubmit(onSubmitParams: OnSubmitParams): void
```

| name | type | description |
| :--------- | :--------- | ----------------------------------------------------------------: |
| name | `string` | Same as `name` in [useFormParams](#useformparams) |
| fields | `object` | Validated fields, same shape as [initialState](#initialstate) |
| setLoading | `function` | Sets the returned `loading` property of [useForm](#useformreturn) |
| notify | `function` | See [NotifyCallback](#notifycallback) |

##### NotifyCallback
Invoked if there is a validation error when calling `handleChange` or `handleSubmit`. Can be manually triggered on `onSubmit` by calling `notify`.

```ts
type NotifyType = "validationErrors" | "submitError" | "submitSuccess"
function notify(type: NotifyType, reason: any): void
```

| name | type | description |
| :----- | :------- | -------------------------------------------------------------------------------------------------------: |
| type | `string` | Type of notification |
| reason | `any` | When type is `validationErrors`, it is a list of field names, Otherwise you set it to whatever you want. |

> Example:

Look at the [live example](#usage).

#### UseFormReturn

| name | type | description |
| :----------- | :--------- | ----------------------------------------------------------------------------: |
| name | `string` | Same as in [useFormParams](#useformparams). |
| fields | `object` | See [FieldValuesAndErrors](#fieldvaluesanderrors) |
| hasErrors | `boolean` | For convenience. `true` if any of the returned fields.{name}.error is `true`. |
| handleChange | `function` | See [ChangeHandler](#changehandler) |
| handleSubmit | `function` | See [SubmitHandler](#submithandler) |
| loading | `boolean` | Controlled by `setLoading` in [onSubmit](#submitcallback) |
| inputs | `object` | See [InputPropGenerators](#inputpropgenerators) |
| validators | `function` | See [Validators](#validators) |

##### FieldValuesAndErrors
Validated field values and their errors.
> Example:

```js
const form = useForm({initialState: {
email: "[email protected]",
age: -2
}})
console.log(form.fields)
// {
// email: {
// value: "[email protected]",
// error: false
// },
// age: {
// value: -2,
// error: truefields
// }
// }
console.log(form.hasErrors) // true
```
##### ChangeHandler
You can call this two ways. Either pass an event as the first argument, or a partial `fields` object. With the latter, you can change multiple values at the same time. E.g.: resetting the form after submit, or any other reason you might have.
```ts
function handleChange(event: React.FormEvent, validations: string[]): void
function handleChange(fields: object, validations: string[]): void
```
| name | type | description |
| :---------- | :---------------- | ---------------------------------------------------------------------------------------------------------------: |
| event | `React.FormEvent` | Standard event. Using `target.{name\|value\|checked}` to infer the intention |
| fields | `object` | Pass a partial `fields` object, if you want to change multiple values at the same time |
| validations | `string[]` | Which `validators` you would like to run. If omitted, only validators with the same event/field name will be run |

> Example:

Look at the [live example](#usage).

##### SubmitHandler
Call to submit the form. Before [onSubmit](#submitcallback) is invoked, [validators](#validators) is run for every form field. If there were any errors, [notify](#notifycallback) is invoked with `type` being `validationErrors`, and `reason` a list of form field names.

```ts
function handleSubmit(): void
```

##### InputPropGenerators

An object, containing properties with the same name as the [HTML input types][input-types-mdn], with some minor differences.

For convenience, since `datetime-local` contains a hyphen (-) character, it is also exposed as `datetimeLocal`, to overcome the need of `"` characters, when accessing it.
I.e.:
```js
const form = useForm()
form.inputs.datetimeLocal == form.inputs["datetime-local"]
```
In addition to the standard input types, there is a `select` type also available.

Each property is a function:

```ts
function inputType(name: string, options: InputTypeOptions): InputPropGeneratorsReturn
```

| name | type | description |
| :------ | :------- | ---------------------------------------------------------------------------: |
| name | `string` | The name of a field. Same as the properties of [initialState](#initialstate) |
| options | `object` | See [InputTypeOptions](#inputtypeoptions) |

> Example:

For examples of all types, you can check [this test suite][input-prop-generator-test-file]

###### InputTypeOptions
An optional object

| name | type | description |
| :------------ | :--------- | -----------------------------------------------------------------------------------------------------------------------------------: |
| value | `string` | Usually, when using radio buttons, values are static. (Each button in the same group must have different values) |
| generateProps | `function` | Provides `name`, `value` and `error` that can be used to generate additional props. Useful, if you want to avoid using `form.fields` |
| formName | `string` | If the input type is `submit`, it can be used to override the name of the form being submitted. |

> Example:
1.
```jsx
const form = useForm(/*...*/)
// *click on option-2*
console.log(form.fields.radio.value) // option-2
return (



{/*You can do it the "traditional" way also*/}


)
```
2.
```jsx
const form = useForm(/*...*/)
return(


({
className: error ? "email-error" : "email",
})
})}
/>
{/*Tip: if your custom Input component takes an error prop, you can try this: */}
_})}/>
{/* This will spread error to Input as well.*/}

{/*Or here is a more complex example for a custom Input component*/}
({
error,
label: error ? `Invalid ${name}` : name,
placeholder: `Type ${name}`
})
})}
/>


)
```

###### InputPropGeneratorsReturn
An object that can be spread on a React input like element.

| name | type | description |
| :------- | :--------- | ----------------------------------------------------------------------------------------------------------------------------------------------: |
| name | `string` | The name of a field. Must be one of the properties in [initialState](#initialstate) |
| id | `string` | By default, same as `name`. If input type is `radio`, it is the same as `value`, to avoid problems in radio groups where `id` would be the same |
| value | `any` | The value of the field |
| onChange | `function` | See [onChange](#changehandler) |
| checked | `boolean` | If input type is `checkbox`, it is the same as `value` |
| onClick | `function` | If input type is `submit`, it is [onSubmit](#submithandler) |

> Example:

```jsx
const form = useForm(/*...*/)

return (


E-mail


{/* This is the same as */}


)
```

### FormProvider

```ts
function FormProvider(formProviderProps: FormProviderProps): JSX.Element
```

#### FormProviderProps

| name | type | description |
| :------------ | :------------- | ------------------------------------------------------------------------------------------: |
| initialStates | `object` | Single place to define all `initialState` for every form. See [InitialState](#initialState) |
| validators | `object` | Single place to define all `validators` for every form. See [Validators](#validators) |
| onSubmit | `function` | Same as [onSubmit](#submitcallback) |
| onNotify | `function` | Same as [onNotify](#notifycallback) |
| children | `ReactElement` | The element you would like to wrap with `FormProvider` |

### getForms
```ts
function getForms() : Forms
```

#### Forms
An object containing all the forms' current values in [FormProvider](#formprovider). Same shape as `initialStates`.

---

## LICENSE

MIT

[build-badge]:
https://img.shields.io/travis/balazsorban44/use-form.svg?style=flat-square
[build]: https://travis-ci.org/balazsorban44/use-form
[coverage-badge]:
https://img.shields.io/codecov/c/github/balazsorban44/use-form.svg?style=flat-square
[coverage]: https://codecov.io/github/balazsorban44/use-form
[version-badge]:
https://img.shields.io/npm/v/another-use-form-hook.svg?style=flat-square
[package]: https://www.npmjs.com/package/another-use-form-hook
[downloads-badge]:
https://img.shields.io/npm/dm/another-use-form-hook.svg?style=flat-square

[npm]: https://www.npmjs.com/
[node]: https://nodejs.org

[npmtrends]: http://www.npmtrends.com/another-use-form-hook
[license-badge]:
https://img.shields.io/npm/l/another-use-form-hook.svg?style=flat-square
[license]:
https://github.com/balazsorban44/use-form/blob/master/LICENSE
[prs-badge]:
https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
[prs]: http://makeapullrequest.com
[donate-badge]:
https://img.shields.io/badge/$-support-green.svg?style=flat-square
[coc-badge]:
https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
[coc]:
https://github.com/balazsorban44/use-form/blob/master/CODE_OF_CONDUCT.md
[github-watch-badge]:
https://img.shields.io/github/watchers/balazsorban44/use-form.svg?style=social
[github-watch]: https://github.com/balazsorban44/use-form/watchers
[github-star-badge]:
https://img.shields.io/github/stars/balazsorban44/use-form.svg?style=social
[github-star]: https://github.com/balazsorban44/use-form/stargazers
[react-hooks]: https://reactjs.org/blog/2019/02/06/react-v16.8.0.html
[input-types-mdn]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
[codesandbox-example]: https://codesandbox.io/s/another-use-form-hook-2mler
[input-prop-generator-test-file]: https://github.com/balazsorban44/use-form/blob/master/src/__tests__/useForm-with-input-prop-generator.test.js
[total-downloads]: https://img.shields.io/npm/dt/another-use-form-hook.svg?label=total%20downloads&style=flat-square