DEV Community

OGURA_Daiki
OGURA_Daiki

Posted on

react-use-form: Type safe React Hooks based form utility

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.

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

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:

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.

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!

Top comments (1)

Collapse
 
hachibeedi profile image
OGURA_Daiki

Documentation is now published! hachibeedi.github.io/mayoiga/index...