Skip to content

Let's make forms a little less painful shall we?

License

Notifications You must be signed in to change notification settings

JBKLabs/react-form

Repository files navigation

React Form

Let's make forms a little less painful shall we?

<Form
  onSubmit={({ values, formValid, resetInputs }) => {
    if (formValid) {
      makeApiCall(values);
    } else {
      resetInputs(['password']);
    }
  }}
>
  <Input name="user.firstName" />
  <Input name="user.lastName" />
  <Input name="email" />
  <Input name="password" />
  <button type="submit">
    Submit
  </button>
</Form>

Installation

npm install @jbknowledge/react-form

React Native Development

Due to some limitations of react-native, form submit events are only supported by the web version of this package at this time. In addition, you should always import from the native submodule when developing in react-native. For example:

import { Form, withFormHandling } from '@jbknowledge/react-form'; // This will not work for react-native projects

import { Form, withFormHandling } from '@jbknowledge/react-form/native';

API

This library exports the following:

  • withFormHandling
  • Form
  • ValidationError

withFormHandling(Component, onChange)

This HoC will provide Component with 4 props:

value: The current value for the input
setValue: A callback function which replace the existing form value
error: The current error message associated with the input, or null
inputProps: An object of meta values which are passed to all inputs within the form

const Input = ({ value, setValue, error, inputProps }) => (
  <div className="form-group">
    {error && inputProps.displayErrors && <div className="form-error">{error}</div>}
    <input
      className="form-input"
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  </div>
);

export default withFormHandling(Input);

In addition, components wrapped by withFormHandling must be provided a name prop, i.e.

<Input name="password" />

more on this below.

Error Handling

By default, react-form does not provide any error handling. The second argument for the HoC withFormHandling or onChange is how you can declare validation rules for your components. onChange is expected to be either a single callback or an array of callbacks and will be called automatically by react-form everytime the value changes. The first arg value will be the next value while props will be all props passed to your wrapped component. This function when called sets the value of error on your behalf. Any error thrown by the onChange callback you provide will automatically be caught and passed to your component via the error prop.

export default withFormHandling(Input, (value) => {
  throw new Error(); // props.error will be set to the thrown error
});

In the event your component has one or more anticipated errors, you can take advantage of the custom ValidationError provided by react-form as a convenience.

export default withFormHandling(Input, (value) => {
  if (isNaN(value)) {
    throw new ValidationError('Please enter a number.'); // props.error will be set to the string provided
  }
});

In addition to value, the onChange callback is also provided all component props. This will allow you to achieve validation similar to the following:

export default withFormHandling(Input, (value, { regex }) => {
  if (regex && !value.match(regex)) {
    throw new ValidationError('Invalid pattern.')
  }
});

If you provide an array of callback functions, each will run until an error is thrown.

export default withFormHandling(Input, [
  () => throw new ValidationError('Invalid'),
  someValidationFunction // never called because the first callback always errors
]);

Input Names

All components which are wrapped by withFormHandling are required to define a value for the name prop.

<Input name="password" />

This requirement serves two purposes:

  • it allows react-form to uniquely identify inputs for state management
  • it allows you to define the structure of the resulting form values

name can be anything, however, it is recommended that you mimic your api's schema. For example, with the following schema:

{
  "user": {
    "name": <string>,
    "age": <number>
  },
  "title": <string>,
}

You can define inputs such as the following:

<Form>
  <Input name="user.name" />
  <Input name="user.age" />
  <Input name="title" />
</Form>

Form

All components wrapped by withFormHandling must be nested underneath one of react-form's Form components like above. Wrapped inputs, however, do not need to be direct children of the Form object; the following is also a valid example:

<Form>
  <div>
    <div>
      <Input name="password" />
    </div>
  </div>
</Form>

Other than props supported by html's form, you can provide the following props to the Form component:

onSubmit({ formValid, values, resetInputs })

This callback function will be called anytime a submit event is fired within the Form component.

NOTE: this callback is not currently supported for react-native, use the onChange callback instead.

formValid: true or false based on all of the nested inputs' error props.
values: All form values; structure is based on the value of nested inputs' name props.
resetInputs: A callback function which will allow you to reset one or more inputs back to their default values. See the Resetting Inputs section for more information.

For example, the following form:

<Form>
  <Input name="nested.value" />
  <Input name="value" />
</Form>

could call your provided onSubmit callback with:

{
  formValid: false,
  values: {
    nested: {
      value: 'example1',
    },
    value: 'example2'
  },
  resetInputs: () => {...}
}

onChange({ formValid, values })

This callback function will be called anytime a form value changes.

formValid: true or false based on all of the nested inputs' error props.
values: All form values; structure is based on the value of nested inputs' name props.
resetInputs: A callback function which will allow you to reset one or more inputs back to their default values. See the Resetting Inputs section for more information.

An example of what this might look like can be seen in the onSubmit section.

inputProps

This is a simple object that passes user defined props directly to the wrapped inputs. For example:

<Form
  inputProps={{
    displayErrors: false,
  }}
>

This inputProps block would be passed as is automatically to all inputs wrapped by withFormHandling making the following possible:

const CustomInput = ({ value, error, setValue, inputProps }) => (
  <div>
    { error && inputProps.displayErrors && <div>{error}</div>}
    <input ... />
  </div>
);

export default withFormHandling(CustomInput);

NOTE: react-form does not perform any optimization on this prop. Guaranteeing referential equality of inputProps is the responsibility of the user.

Resetting Inputs

In both of the provided Form lifecycle hooks, onChange and onSubmit, you are able to reset the value of one or more inputs back to their default values via the provided resetInputs(patterns) function.

patterns: An array of glob patterns which will be matched with your inputs' names.

Given the following form:

<Form
  onChange={() => {...}}
>
  <Input name="username" defaultValue="johndoe34" >
  <Input name="address.street" >
  <Input name="address.city" >
  <Input name="address.state" >
  <Input name="address.zip" >
</Form>
  • resetInputs(['address.state', 'address.city']) will reset the values of address.state and address.city back to empty strings

  • resetInputs(['username']) will reset the value of username to johndoe34

  • resetInputs(['address.*']) will reset the value of all inputs except username back to empty strings

  • resetInputs() which is the same as resetInputs(['*']) will reset all inputs back to their default values

Contributors

react-form was built and is maintained by JBKLabs, JBKnowledge Inc's research and development team.

Licensing

This package is licensed under Apache License, Version 2.0. See LICENSE for the full license text.