Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

another-use-form-hook

Package Overview
Dependencies
Maintainers
1
Versions
57
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

another-use-form-hook

A React hook 🎣 for easy form handling

  • 4.1.1
  • latest
  • Source
  • npm
  • Socket score

Version published
Maintainers
1
Created
Source

another-use-form-hook

A React hook 🎣 for easy form handling


Build Status Code Coverage version downloads Total downloads MIT License PRs Welcome Code of Conduct

Watch on GitHub Star on GitHub


Table of Contents

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, 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)
  2. Using form.fields.{name}.{value|error} and form.handleChange functions

NOTE: The example below is available live at CodeSandbox

Let's see a complex example to understand how it works:

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 (
    <form onSubmit={form.handleSubmit}>
      {/* option 1 (control all the props with a one-liner)*/}
      <fieldset>
        <legend>Option 1</legend>

        <label htmlFor="email">
          {form.fields.email.error ? "Invalid" : ""} email
        </label>
        <input {...form.inputs.email("email")} />

        <label htmlFor="departure">
          {form.fields.arrival.error ? "Invalid" : ""} arrival
        </label>
        <input
          {...form.inputs.date("arrival")}
          // You can override props by simply defining them last
          onChange={e => form.handleChange(e, ["minOneNight"])}
        />
      </fieldset>

      {/* option 2 specify id, type, name, value props manually*/}
      <fieldset>
        <legend>Option 2</legend>
        <label htmlFor="arrival">
          {form.fields.arrival.error ? "Invalid" : ""} arrival
        </label>
        <input
          type="date"
          id="arrival"
          name="arrival"
          value={form.fields.arrival.value}
          onChange={e => form.handleChange(e, ["minOneNight"])}
        />

        <label htmlFor="departure">
          {form.fields.departure.error ? "Invalid" : ""} departure
        </label>
        <input
          type="date"
          id="departure"
          name="departure"
          value={form.fields.departure.value}
          onChange={e => form.handleChange(e, ["minOneNight"])}
        />
      </fieldset>

      {/* also from option 1 */}
      <button {...form.inputs.submit()} disabled={form.loading}>
        Submit
      </button>
    </form>
  );
};

ReactDOM.render(<App />, document.querySelector("#root"));

Documentation

useForm

  useForm(useFormParams: UserFormParams): UseForm
UseFormParams
nametypedescription
namestringRefer to one of the forms in formProviderProps
initialStateobjectSee InitialState
validatorsfunctionSee Validators
onSubmitfunctionSee SubmitCallback
onNotifyfunctionSee NotifyCallback
InitialState

An object containing the default value of every field in a form.

Example:

useForm({
  initialState: {
    email: "",
    name: "",
    address: "",
    age: 0
  }
})

If name is defined, you can refer to initialStates.{name} in formProviderProps.

Example:

//...
<FormProvider 
  initialStates={{
    login: {
      email: "email@example.com",
      password: ""
    }
  }}
>
//...
const form = useForm({name: "login"})
console.log(form.fields.email.value) // email@example.com
Validators

This function is invoked before onChange and onSubmit. 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.

function validators(fields: object, isSubmitting: boolean): Validations
nametypedescription
fieldsobjectAn object with the same shape as initialState
submittingbooleanSet to true, when called before handleSubmit
validationsobjectSee 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:

{
  email: submitting
    ? isValidEmail(fields.email)
    : typeof fields.email === "string"
}

You can also look at the live example.

SubmitCallback

Invoked when handleSubmit is called and there were no validation issues.

function onSubmit(onSubmitParams: OnSubmitParams): void
nametypedescription
namestringSame as name in useFormParams
fieldsobjectValidated fields, same shape as initialState
setLoadingfunctionSets the returned loading property of useForm
notifyfunctionSee NotifyCallback
NotifyCallback

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

type NotifyType = "validationErrors" | "submitError" | "submitSuccess"
function notify(type: NotifyType, reason: any): void
nametypedescription
typestringType of notification
reasonanyWhen type is validationErrors, it is a list of field names, Otherwise you set it to whatever you want.

Example:

Look at the live example.

UseFormReturn
nametypedescription
namestringSame as in useFormParams.
fieldsobjectSee FieldValuesAndErrors
hasErrorsbooleanFor convenience. true if any of the returned fields.{name}.error is true.
handleChangefunctionSee ChangeHandler
handleSubmitfunctionSee SubmitHandler
loadingbooleanControlled by setLoading in onSubmit
inputsobjectSee InputPropGenerators
validatorsfunctionSee Validators
FieldValuesAndErrors

Validated field values and their errors.

Example:

  const form = useForm({initialState: {
    email: "email@example.com",
    age: -2
  }})
  console.log(form.fields)
  // {
  //  email: {
  //   value: "email@example.com",
  //   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.

function handleChange(event: React.FormEvent, validations: string[]): void
function handleChange(fields: object, validations: string[]): void
nametypedescription
eventReact.FormEventStandard event. Using target.{name|value|checked} to infer the intention
fieldsobjectPass a partial fields object, if you want to change multiple values at the same time
validationsstring[]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.

SubmitHandler

Call to submit the form. Before onSubmit is invoked, validators is run for every form field. If there were any errors, notify is invoked with type being validationErrors, and reason a list of form field names.

function handleSubmit(): void
InputPropGenerators

An object, containing properties with the same name as the HTML input types, 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.:

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:

function inputType(name: string, options: InputTypeOptions): InputPropGeneratorsReturn
nametypedescription
namestringThe name of a field. Same as the properties of initialState
optionsobjectSee InputTypeOptions

Example:

For examples of all types, you can check this test suite

InputTypeOptions

An optional object

nametypedescription
valuestringUsually, when using radio buttons, values are static. (Each button in the same group must have different values)
generatePropsfunctionProvides name, value and error that can be used to generate additional props. Useful, if you want to avoid using form.fields
formNamestringIf the input type is submit, it can be used to override the name of the form being submitted.

Example:

const form = useForm(/*...*/)
// *click on option-2*
console.log(form.fields.radio.value) // option-2
return (
  <radiogroup>
    <input {...inputs.radio('radio', { value: 'option-1' })}/>
    <input {...inputs.radio('radio', { value: 'option-2' })}/>
    {/*You can do it the "traditional" way also*/}
    <input
      {...inputs.radio('radio')} 
      value='option-3'
    />
  </radiogroup>
)
const form = useForm(/*...*/)
return(
  <div>
    <input
      {...form.inputs.email("emailField", {
        generateProps: ({error}) => ({
          className: error ? "email-error" : "email",
        })
      })}
    />
    {/*Tip: if your custom Input component takes an error prop, you can try this: */}
    <Input {...form.inputs.email("emailField", {generateProps: _ => _})}/>
    {/* This will spread error to Input as well.*/}

    {/*Or here is a more complex example for a custom Input component*/}
    <Input 
      {...form.inputs.email("emailField", {
        generateProps: ({error, name}) => ({
          error,
          label: error ? `Invalid ${name}` : name,
          placeholder: `Type ${name}`
        })
      })}
    />
  </div>
)
InputPropGeneratorsReturn

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

nametypedescription
namestringThe name of a field. Must be one of the properties in initialState
idstringBy 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
valueanyThe value of the field
onChangefunctionSee onChange
checkedbooleanIf input type is checkbox, it is the same as value
onClickfunctionIf input type is submit, it is onSubmit

Example:

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

return (
  <div>
    <label htmlFor="emailField">E-mail</label>

    <input {...form.inputs.email("emailField")}/>
    {/* This is the same as */}
    <input
      name="emailField"
      id="emailField"
      type="email"
      onChange={form.handleChange}
      value={form.fields.emailField.value}
    />
  </div>
)

FormProvider

  function FormProvider(formProviderProps: FormProviderProps): JSX.Element
FormProviderProps
nametypedescription
initialStatesobjectSingle place to define all initialState for every form. See InitialState
validatorsobjectSingle place to define all validators for every form. See Validators
onSubmitfunctionSame as onSubmit
onNotifyfunctionSame as onNotify
childrenReactElementThe element you would like to wrap with FormProvider

getForms

  function getForms() : Forms
Forms

An object containing all the forms' current values in FormProvider. Same shape as initialStates.


LICENSE

MIT

Keywords

FAQs

Package last updated on 07 Feb 2020

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc