react-use-form: Type safe React Hooks based form utility
Tags: react, hooks, form
Published: 2019-04-01 05:48:57

---
title: react-use-form: Type safe React Hooks based form utility
published: true
description: Introduction of a new TypeScript native React hooks based form library
tags: React,Hooks,Form
---

Managing forms in React is hard but boring task.
In addition, you are going to suffer from type unsafeness due to of native DOM input restrictions if you preferred TypeScript.

There are several libraries to solve the problem. react-form, redux-form, formik...
Now I added the new one which names Mayoiga.

https://github.com/hachibeeDI/mayoiga

The charasteristics of Mayoiga are below:

- React Hooks base (useForm to summon form component)
- Strict type check (I love TypeScript!)
- Simple API for less learning cost

## How to use

This is minimum working example.

```typescript
import * as React from 'react';
import {FC} from 'react';

import {useForm, createFormScope} from 'mayoiga';
import {Input, NumberInput} from 'mayoiga/lib/forms';

const required = (target: string) => (target.length === 0 ? 'required' : undefined);

const between = (min: number, max: number) => (target: number) => {
  if (target < min) {
    return `more than ${min}`;
  }
  if (target > max) {
    return `less than ${max}`;
  }
};

const choice = function<T>(...candidates: Array<T>) {
  return (target: T) => (candidates.includes(target) ? undefined : 'You should choose from the candidates.');
};

const INITIAL_FORM_STATE = {
  name: '',
  age: 13,
  race: 'fish',
};

const {context, scope} = createFormScope<typeof INITIAL_FORM_STATE>();

const DemoForm = scope(props => {
  const {Form, Field} = useForm(context);
  return (
    <Form onSubmit={value => console.log(value)}>
      <Field name="name" component={Input} validations={[required]} />
      <Field name="age" component={NumberInput} validations={[between(5, 20)]} />
      <Field name="race" component={Input} validations={[choice('fish', 'squid', 'octopus')]} />

      <button disabled={!props.touched || Object.values(props.errors).some(e => !!e.length)}>submit</button>
    </Form>
  );
});

export default function DemoApp() {
  return <DemoForm initialState={INITIAL_FORM_STATE} onSubmit={value => alert(`submit ${JSON.stringify(value)}`)} />;
}
```


Let me explain some lines that is essential. (These are still working in improve progress so may be changed)

### Scope

```typescript
const {context, scope} = createFormScope<typeof INITIAL_FORM_STATE>();
```

`createFormScope` is to create a scope which allows share the form state between Form and Fields.
A component was included in `scope` is going to have two additional props `initialState` and `onSubmit`. Those values are working you can see `<DemoApp />`.

### Form

In form layer component, you can also handling `onSubmit` on `<Form />` component in case you need.  
`<Field />` requires `name` and `component`, optionally `validations`.

`name` of the Field isn't just typed as `string` but `keyof State`. So that if you had typo like as `<Field name="rage" ...` TypeScript compiler warn like "rage is not a type of 'keyof typeof INITIAL_FORM_STATE'".

`component` is also strictly typed. A component should follow the signature named `InputProtocol<State, Name>` which is defined as below:

```typescript
export type InputProtocol<S, Name extends keyof S, DelegatedProps> = Omit<InputHTMLAttributes<any>, 'name' | 'onChange' | 'value'> & {
  name: Name;
  value: S[Name];
  errors: ReadonlyArray<string>;
  touched: boolean;
  onChange(name: Name, value: S[Name]): void;
};
```

I have written some glue for native DOM component. You can import those from `mayoiga/lib/forms`.

You can also write glue for any components even if the component is complex which returns not primitive value i.e. Array or Object.

#### Validation

`validations` of `<Field />` is a array of `Validator`. Definition of `Validator` is below.

```typescript
type Validator<S, Name extends keyof S> = (target: S[Name], record: S) => undefined | string;
```

Validator can get the value of the target and also all other records. You can return string as error message.


## Lastly

Mayoiga is under development and no documentation so far (because of my English skill XD) though any questions/issues/requests are welcome. :)

However, I think anyone who hate managing week typed input with boilerplate codes are interested in the concept.

Thanks!