react-proforma

React Proforma helps you build simple to complex web forms with ease in React. -- Simplicity where you want it. Flexibility where you need it.

Usage no npm install needed!

<script type="module">
  import reactProforma from 'https://cdn.skypack.dev/react-proforma';
</script>

README

Banner

Simplicity where you want it. Flexibility where you need it.


npm Travis (.com) branch npm bundle size Snyk Vulnerabilities for GitHub Repo Coveralls github

React Proforma

Build simple to complex web forms with ease in React.

Easily manage multiple form fields, validation, focus handling, and form submission. Use custom components (styled with css-in-js or from a UI library) or standard react elements anywhere you like. React Proforma is a complete form-solution that has been performance-optimized, is fully documented, and will make building your next React web form a breeze!

Check out the video tutorial/demonstration on YouTube!


Table of Contents


Version 2.0.0 - BREAKING CHANGES

  • In the 'Proforma.handleSubmit' prop, React Proforma will now invoke your function with a different configuration of arguments to give your form submission handler function greater flexibility and access to more functionality. See the Proforma component API for details.

Installation

Install using npm:

npm install react-proforma

Or yarn:

yarn add react-proforma


Basic Usage

We'll start by building up a very basic, no-frills example with a single field and a submit button, just to give you a feel for what's going on. After that, I'll demonstrate code that is more like what you would normally use, including using React Proforma's custom form elements that are all hooked up internally.

Please note:

  • I prefer using class components because I find they better organize my code. You are, of course, free to use function components.
  • All exports are named exports, so don't forget your curly brackets on your import statements!

Step 1: Build the scaffolding:

import React from 'react';
import { Proforma } from 'react-proforma';

class DemoForm1 extends React.Component {
  renderForm(proformaBundle) {
    /* to be filled in shortly */
  }

  render() {
    return (
      <Proforma>
        {this.renderForm}
      </Proforma>
    );
  }
}
  • Notice how the component's render method is returning the Proforma component, and inside that is a reference to a function that I've called renderForm (which is currently empty).
  • The sole argument passed to renderForm is the bundle of methods and properties you will need for your form to function. I've assigned this argument the name proformaBundle.
  • Please note that there are no parentheses next to this.renderForm! This is a function reference, not a function invocation.

Step 2: Add config and handleSubmit props to the Proforma component

import React from 'react';
import { Proforma } from 'react-proforma';

class DemoForm1 extends React.Component {
  renderForm(proformaBundle) {
    /* to be filled in shortly */
  }

  render() {
    return (
      <Proforma
        config={{
          initialValues: {
            field1: ''
          }
        }}
        handleSubmit={(values) => {
          console.log('The form has been submitted!', values);
        }}
      >
        {this.renderForm}
      </Proforma>
    );
  }
}
  • Proforma.config accepts an object (note the double curly braces) with one required property (initialValues) and a few optional properties (we'll learn more about those later).
    • initialValues is a required property, whose value must be an object, whose properties must correspond with the names of your form fields (i.e. name, email, password, etc.), and whose values are the initial value you'd like to set for each respective form field.
    • In this example, I have a single field name ('field1'), and I've set it to have an initial value of '' (empty string).
    • Please note that form field values in React and HTML can only be boolean (for checkboxes) or strings. So if you pass in any other type of value to initialValues, it will be ignored, and empty string '' will be used.
  • Proforma.handleSubmit is any function you want to have executed when the form is submitted. See the api for details, including what arguments are passed to your handleSubmit function on execution.
  • There will be nothing on your screen because renderForm isn't returning anything. Let's fix that next.

Step 3: Fill in renderForm

NOTE: This is going to look ugly, but React Proforma's custom form elements will clean this all up very quickly! I just wanted to show you the manual set up first.

import React from 'react';
import { Proforma } from 'react-proforma';

class DemoForm1 extends React.Component {
  renderForm(proformaBundle) {
    const {
      values,
      handleSubmit,
      handleChange,
      handleFocus,
      handleBlur
    } = proformaBundle;

    return (
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          name="field1"
          value={values.field1}
          onChange={handleChange}
          onFocus={handleFocus}
          onBlur={handleBlur}
        />
        <button type="submit">Submit</button>
      </form>
    );
  }

  render() {
    return (
      <Proforma
        config={{
          initialValues: {
            field1: ''
          }
        }}
        handleSubmit={(values) => {
          console.log('The form has been submitted!', values);
        }}
      >
        {this.renderForm}
      </Proforma>
    );
  }
}
  • We begin by destructuring out values, handleSubmit, handleChange, handleFocus, and handleBlur from proformaBundle.
  • renderForm then returns a regular React form element.
  • I manually assign the various handlers to their respective event hooks in the form and input elements, set the name prop on the input element to be 'field1', and hook up the value prop of the input to values.field1.

At this point, we have a fully functioning form. The input is completely controlled, and you can submit the form by clicking the submit button (your console should display the message "The form has been submitted!", followed by the form values).

Demo Form 1

Check out the complete code for this form here.


Normal Usage

Now that you've seen the nuts and bolts, let's clean it up and create a more complete example, with multiple fields and validation.

Form Mark-Up

Introducing Form, Field, and Submit

We'll create a sign-up form with name, email address, and password fields to start.

import React from 'react';
import './DemoForm2.css';
import { Proforma, Form, Field, Submit, Debug } from 'react-proforma';

class DemoForm2 extends React.Component {
  renderForm() {
    return (
      <div className="outer-wrapper">        
        {/* === FORM STARTS HERE === */}
        <Form>
          <div className="field-row">
            <label htmlFor="name-field">Name:</label>
            <Field
              name="name"
              type="text"
              className="form-field"
              id="name-field"
              placeholder="E.g. Billy Bob"
            />
          </div>
          <div className="field-row">
            <label htmlFor="email-field">Email:</label>
            <Field
              name="email"
              type="text"
              className="form-field"
              id="email-field"
              placeholder="E.g. billy@bob.com"
            />
          </div>
          <div className="field-row">
            <label htmlFor="password-field">Password:</label>
            <Field
              name="password"
              type="password"
              className="form-field"
              id="password-field"
            />
          </div>
          <Submit className="submit-button" />
        </Form>
        {/* === FORM ENDS HERE === */}        
        <div className="debug-wrapper">
          {/* DEBUG COMPONENT */}
          <Debug />
        </div>
      </div>
    );
  }

  render() {
    return (
      <Proforma
        config={{
          initialValues: {
            name: '',
            email: '',
            password: ''
          }
        }}
        handleSubmit={(values) => {
          console.log('The form has been submitted!', values);
        }}
      >
        {this.renderForm}
      </Proforma>
    );
  }
}

With some styling divs here and there, this code produces the following form:

(Grab the css file if you want to follow along exactly.)

Demo Form 2a

NOTE: That window you see on the right is a Debug component I made in case you ever want to see your form's current state (i.e. values/errors/touched). Just import { Debug } from 'react-proforma' and insert the component anywhere inside the <Proforma>...</Proforma> tree.

The styling divs clutter things up a little, but I want you to just focus on the props passed to the Proforma component, as well as the Form (capital 'F') component inside the renderForm method.

  • Proforma props
    • Again we have our config prop. Inside config is an object with a single property (for now) called 'initialValues', which contains the names of all the form fields our form is going to have, and their respective initial values.
    • Our handleSubmit is again going to just console log our values.
  • Looking inside renderForm: Form, Field, and Submit
    • Here is our first exposure to the Form, Field, and Submit components imported from React Proforma.
    • Using these components, you no longer have to wire up your form functionality manually -- these components are all hooked up for you.
    • For Field, all you have to do is pass in a name (that corresponds with one of the keys on your initialValues object) and a type (defaults to "text") as props, along with whatever other props you'd want passed down to the input element that will be returned (e.g. id, className, style, placeholder, maxLength, etc.)
    • (Much more customization is possible with all components exported from React Proforma. See the API for more information.)

See an example that shows each React Proforma component in action.

Alright, so everything looks good, but we're going to need some validation.

Thankfully, React Proforma makes that almost too easy!

Validation

Enter the validationObject.

First, let's do it the long way:

<Proforma
  config={{
    initialValues: {
      name: '',
      email: '',
      password: ''
    },
    validationObject: {
      name: (values) => {
        const { name } = values;
        const errors = [];
        if (name.length === 0) {
          errors.push('This field is required.');
        }
        if (!/^[a-zA-Z\s]*$/.test(name)) {
          errors.push('Your name can only contain letters and spaces.');
        }
        if (errors.length > 0) {
          return errors;
        }
        return null;
      }
    }
  }}
  handleSubmit={(values) => {
    console.log('The form has been submitted!', values);
  }}
>
  {this.renderForm}
</Proforma>

So what's going on here.

  • validationObject is another property on the object passed to the config prop. Here, the keys are one of the form field names (I used the 'name' form field here), and the values are a function which accepts all of the current form values, and returns either an array of strings or null.
  • In this example, I destructure the 'name' value from values, set up an empty errors array, and then run two validation tests on 'name':
    1. Make sure the length of 'name' is not zero (i.e. this field is required)
    2. Make sure 'name' contains only letters and spaces (with a regex test)
  • If either of these produce an error, an error message is pushed into the errors array.
  • Finally, if there are any errors in the errors array, the errors array is returned. Otherwise, null is returned.

So, obviously this is a lot of typing. This is why I created a field validation helper called fieldValidator. Simpy import it: import { fieldValidator } from 'react-proforma', and use it to produce the same logic as above:

validationObject: {
  name: (values) => {
    return fieldValidator(values.name)
      .required()
      .regex(/^[a-zA-Z\s]*$/, 'Your name can only contain letters and spaces.')
      .end();
  }
}
  • Inside the function, pass to fieldValidator the value you want to validate (in this case, it's 'values.name'), and then chain on whatever validation methods you want.
  • Here, I've used .required() and .regex().
  • All validator methods have default error messages, but you can optionally use your own, as I did here with the regex() call.
  • See the fieldValidator API for all of the methods available to you.

NOTE: You must add .end() to the end of the fieldValidator chain, and you must return the entire chain from the function! (I.e. return fieldValidator(values.name) ..... .end())

With this new tool, let's go ahead and add email and password validation in just a few seconds:

validationObject: {
  name: (values) => {
    return fieldValidator(values.name)
      .required()
      .regex(
        /^[a-zA-Z\s]*$/,
        'Your name can only contain letters and spaces.'
      )
      .end();
  },
  email: (values) => {
    return fieldValidator(values.email)
      .required()
      .email()
      .end();
  },
  password: (values) => {
    return fieldValidator(values.password)
      .required()
      .regex(
        /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/,
        'Please enter a stronger password!'
      )
      .end();
  }
}

NOTE: I pass the entire values object to each validation field so that you can access other values if that's what your validation requires. For more information, see the fieldValidator API.

Now we can take our form for a spin. Enter valid and invalid values, and see the 'Errors' object update in the Debug display.

For example: Demo Form 2b

NOTE: The 'touched' value for each field name turns true when a field receives and then loses focus. This is useful for displaying errors to the user, which is typically done only if 'touched' is true for that field name.

So this is all working quite nicely. But let's go ahead and try something a little more challenging, and see how React Proforma performs.

Cross-Field Validation

Let's say at this point we decide to add a second password field. That means we have to add a new field to our form, as well as attempt cross-field validation to make sure the passwords match. Once again, React Proforma makes it all easy.

Let's start by updating our form.

Add the following to your Form inside renderForm, before the Submit button:

<div className="field-row">
  <label htmlFor="password2-field">Re-enter Password:</label>
  <Field
    name="password2"
    type="password"
    className="form-field"
    id="password2-field"
  />
</div>

NOTE: The 'name' I used for this new field is 'password2'.

And update your initialValues object:

initialValues: {
  name: '',
  email: '',
  password: '',
  password2: ''
}

REMINDER: All form field names must be present in the Proforma.config.initialValues object, or it won't be included in any of React Proforma's functionality.

Now we need to add validation to 'password2' to make sure it matches 'password'.

Add the following property to your Proforma.config.validationObject:

password2: (values) => {
  return fieldValidator(values.password2)
    .required()
    .custom(() => {
      if (values.password2 !== values.password) {
        return 'Your passwords must match exactly!';
      }
    })
    .end();
}

Here we are making use of the custom() method chained to fieldValidator(). custom() takes a function as it's sole argument, which will be executed by Proforma during validation. If your function returns a string value, that value will be added to the current error messages for that field.

But we can make things even more concise in this case, by making use of the .equals() method:

password2: (values) => {
  return fieldValidator(values.password2)
    .required()
    .equals(values.password, 'Your passwords must match exactly!')
    .end();
}

(See the docs for all available chaining methods and their specific signatures.)

And that's all it takes to perform cross-field validation with React Proforma!

Here's what we've created so far:

Demo Form 2c

Displaying Errors

The normal way of displaying errors in React is to write up some conditional checks inside your jsx that looks something like:

<div>
  {touched.email && errors.email &&
    errors.email.map((error) => ...) /* return component that displays error */
  }
</div>

But that's kind of ridiculous to have to do for every single form field.

Enter fieldError()

Use it like this:

  1. import { fieldError } from 'react-proforma';

  2. Create a component that will receive each individual error message as its only prop. For example:

    function FieldError(props) {
      return <p className="field-error">{props.error}</p>;
    }
    
  3. Under each field row, add the following:

    {fieldError('<FIELD NAME>', FieldError)}
    

    As in:

    <div className="field-row">
      <label htmlFor="name-field">Name:</label>
      <Field
        name="name"
        type="text"
        className="form-field"
        id="name-field"
        placeholder="E.g. Billy Bob"
      />
    </div>;
    {fieldError('name', FieldError)} /* <--- */
    

NOTE: You could insert fieldError() anywhere of course.

And just like that we have:

Demo Form 2d

Check out the complete code for this form here.

Wrap-up

There's a lot more built into React Proforma, including other pre-wired components (Select, Checkbox, Radio, Textarea, and Reset), using custom components (like from a UI library or created by a css-in-js library, or just components you've created yourself) instead of standard React elements, and giving you access to more of your form's functionality through the methods and properties inside the ProformaBundle.

Browse the API to learn about everything that React Proforma offers you, and check out some more examples.


Typescript

This entire library was written in Typescript, so in addition to being fully typed, I also take full advantage of Typescript's other features wherever possible.

Usage in Typescript is pretty much the same as it is in vanilla JS, with two notable exceptions:

  1. The Proforma component is a generic class, so you just have to pass in the type that represents your form values, and Proforma propogates that around wherever possible:

    type FormValues = {
      name: string;
      email: string;
      password: string;
      newsletter: boolean;
    };
    
    /* Skip to component render method */
    render() {
      return (
        <Proforma<FormValues>
          config={{...}}
          handleSubmit={() => {...}}
        >
          {this.renderForm}
        </Proforma>
      );
    }
    

    If you didn't know how to pass in a type variable to a component in JSX before, now you know!

  2. In your renderForm method (or whatever function returns your form mark-up), in order to have your proformaBundle argument fully typed, you have to import ProformaBundle, which is a generic type that expects your FormValues again as a type variable. As in:

    import { ProformaBundle } from 'react-proforma';
    
    type FormValues = {
      name: string;
      email: string;
      password: string;
      newsletter: boolean;
    };
    
    /* Skip to renderForm method */
    renderForm(proformaBundle: ProformaBundle<FormValues>) {
      return (
        /* form markup */
      );
    }
    

    Or with in-line destructuring:

    renderForm({
      values,
      errors,
      touched,
      handleSubmit,
      handleReset,
      /* whatever else you need from ProformaBundle */
    }: ProformaBundle<FormValues>) {
      return (
        /* form markup */
      );
    }
    

Going further

Using Custom Components/UI LIbs

Very often you will likely want to use custom components or UI library components instead of standard React form elements. React Proforma makes this very easy to use.

Here's an example of how to integrate a css-in-js component (e.g. using the Styled Components library), as well as a Material-UI component, into React Proforma:

  1. Import whatever you need from the respective libraries and React Proforma:
import styled from 'styled-components';
import { TextField } from '@material-ui/core';
import { Field } from 'react-proforma';
  1. Create your styled-component (or your own custom component):
const StyledInput = styled.input`
  color: blue;
  font-size: 35px;
  font-weight: 'bold';
  padding: 8px;
  margin-top: 5px;
`;
  1. Pass these components into Field in the 'component' prop, and add whatever other props you would normally pass to that component:
<Field 
  name="email"
  component={TextField}
  type="text"
  required
  label="Email"
  margin="normal"
  style={{ marginTop: '0', width: '100%' }}
/>

<Field
  name="password"
  component={StyledInput}  
  type="text"  
  required
  placeholder="Password field"  
/>

You can see a complete example here.

Using Proforma.config.customOnChange

  1. The customOnChange object, along with your component state, can be used to fulfill special validation requirements.
    • I created this example to show how using the 'customOnChange' object makes password-strength validation easy.
  2. You can use customOnChange to restrict user input by only allowing a specific kind of input in a field.
    • I created this example to show how React Proforma makes that easy.

TODO


API

Components

Proforma

Proforma is the core component of the React Proforma library. It is a React component that you return from your own component's render method (or returned from your functional component). It expects as a single child a function that will return your actual form markup. The child function can be declared in-line as an anonymous function, but I recommend referencing a named function instead, for organization, clarity, and efficiency.

The child function will be executed by Proforma with the entire ProformaBundle, so you can access it (or any part of it) inside the child function that you pass in.

For example:

NOTE: The child function does NOT HAVE PARENTHESES next to it. This is a function reference, not a function invocation.

import React from 'react';
import { Proforma } from 'react-proforma';

class MyForm extends React.Component {
  renderForm(proformaBundle) {
    /* extract any piece of the ProformaBundle */
    const {
      values,
      touched,
      errors,
      handleSubmit
    } = proformaBundle;

    return (
      <form onSubmit={handleSubmit}>
        {/* form markup */}
      </form>
    );
  }

  render() {
    return (
      <Proforma
        config={{...}}
        handleSubmit={() => {...}}
      >
        {this.renderForm}
      </Proforma>
    );
  }
}
  • Proforma.config

    • Proforma.config.initialValues: object

      • This object defines all of the form field names you wish to include in the Proforma internal functionality (change handling, focus/blur handling, etc.).
      • If a name key is not on this object, it will not be available in your form!
      • The 'initialValues' object is also where, as the name implies, you can set the initial value of any form field name. You can either set values to empty strings, a boolean value (for checkboxes), or grab values from your components props. For example:
      initialvalues: {
        name: this.props.userName || '',
        email: this.props.userEmail || '',
        password: '',
        newsletter: true
      }
      
      • If you pass in a value that is neither of type 'boolean' nor of type 'string', the value will default to empty string ''.
    • Proforma.config.validationObject: object

      • This is an optional object that allows you to define the validation you'd like to have performed on any of your form fields.

      • The keys must correspond with whichever form field names you wish to have validated, and the values are functions that accept the current 'values' object of your form as its sole argument. The return value from each function must either be an array of strings (containing the error messages to be placed in the 'errors' object for that field, even if there is only one error message), or null. For example:

        validationObject: {
          password: (values) => {
            /* 
              Perform check on values.password.
              Return array of error messages , or null.
            */
          };
        }
        
      • You can either do this manually, or make use of the fieldValidator helper function to streamline this process.

      • NOTE: I make the entire values object available to each validationObject property so that you can reference other field name values if you need to.

    • Proforma.config.customOnChangeObject: object

      • There might be times when you want to have your own function executed when a form field is changed instead of Proforma's internal change handler method. This need may arise if you want to perform multi-stage validation (.e.g a password strength indicator), or restrict the user's input in a particular form field. (See some use-cases here.)
      • Each property on the customOnChangeObject should be a name that corresponds with one of your form field names.
      • Each value on the customOnChangeObject should be a function that accepts two arguments:
        1. event: React.ChangeEvent -- the change event emitted by React.
        2. setValues: function -- a function that allows you to set the value of one or more of your form fields. NOTE: Since all form fields inside Proforma are controlled, you will have to use setValues to update that particular form field yourself. For example:
      customOnChangeObject: {
        name: (event, setValues) => {
          const { value } = event.target;
      
          /*
            Any logic you want to perform on 'value'..
          */
          const upperCaseValue = value.toUpperCase();
      
          /* IMPORTANT (or UI won't update) */
          setValues({ password: upperCaseValue });
        };
      }
      
    • Proforma.config.onValidateCallbacksObject: object

      • There are some situations where it is useful to have a callback function called on every firing of a field's validation logic. If you need that functionality, you can access it here.
      • Each property on the onValidateCallbacksObject should be a name that corresponds with one of your form field names.
      • Each value on the onValidateCallbacksObject should be a function that accepts no arguments and returns void. I.e. callback: () => void.
    • Proforma.config.resetTouchedOnFocus: boolean

      • A simple boolean flag that, if true, resets the 'touched' value of each field name back to false when that field receives focus.
      • Defaults to false.
    • Proforma.config.validateOnChange: boolean

      • A simple boolean flag that, if false, will not run validation as the user types (changes) form values. This means validation will only be run when a field receives and then loses focus.
      • Defaults to true.
  • Proforma.handleSubmit

    • This function will be executed by React Proforma when your form is submitted.
    • I call event.preventDefault() for you, so don't worry about including that in your own handleSubmit.
    • I also put the execution of your handleSubmit function inside a conditional that prevents a user from submitting the form many times with multiple clicks.
      • NOTE: If your handleSubmit function makes use of the 'setSubmitting' function (see below) to set the 'isSubmitting' property to false, and for some reason you've kept the form component on the screen, the user WILL be able to submit the form again unless you make use of the 'submitCount' property (see below) to control a user's ability to re-submit your form.
    • It can be async if you need use of await in your function logic.
    • Your handleSubmit function will be executed with the following arguments being made available to your function:
      1. 'values': object -- The 'values' object that represents all the current values of your form at the moment your form is submitted. You can, of course, access into the 'values' object with dot or bracket notation (i.e. values.email, or values['email']).
      2. HandleSubmitBundle: object -- This is an object that contains a subset of the methods and properties that are available in the ProformaBundle object. They are: setSubmitting, setValues, setComplete, resetFields (this is handleReset in the ProformaBundle), and submitCount. In addition to these methods/properties, you can also find the 'event' object (React.FormEvent | React.SyntheticEvent) emitted by React when the form is submitted, should you need it. Note that I do the event.preventDefault() for you, so access the event object for anything other than that.
    • Example handleSubmit function:
    <Proforma
      config={{...}}
      handleSubmit={async (values, {setSubmitting}) => {
        /* Post values to server */
        try {
          const response = await axios.post(serverUrl, values);
    
          if (response.data.message === 'success') {
            /* redirect user somewhere */
          } else {
            console.log('Something went wrong!', response.data.error);
            /* use setSubmitting to flip the 'isSubmitting' property inside ProformaBundle */
            setSubmitting(false);
          }
        } catch (error) {
          console.log('Something went wrong!', error);
          /* use setSubmitting to flip the 'isSubmitting' property inside ProformaBundle */
          setSubmitting(false);
        }
      }}
    >
      {this.renderForm}
    </Proforma>
    

Form

The Form component comes automatically wired up with Proforma's 'handleSubmit' function that you would normally have to wire up yourself by grabbing it from the PorformaBundle.

That is, this:

class MyForm extends React.Component {
  renderForm(proformaBundle) {
    return (
      <form onSubmit={proformaBundle.handleSubmit}>
        {/* ... */}
      </form>
    );
  }

  render() {
    /* etc. etc. */
  }
}

...is equal to this:

class MyForm extends React.Component {
  renderForm(proformaBundle) {
    return (
      <Form>
        {/* ... */}
      </Form>
    );
  }

  render() {
    /* etc. etc. */
  }
}

Simply import Form from React Proforma:

import { Form } from 'react-proforma';
  • Form.component: React.ComponentType (optional)
    • A custom component you'd like to use instead of having a standard form element returned from Form. See here for how to use this.
  • Form...otherProps
    • Any additional props passed to Form will be forwarded onto the resulting React form element.

Field

This component will be your go-to for all input types that are not checkboxes, radio buttons, selects (dropdowns), or textareas. (I've separated those into their own components.) It comes automatically wired up as a controlled component.

The Field component must be passed a 'name' prop corresponding with one of the form field names in your 'initialValues' object in your Proforma.config. All other props will be forwarded to the resulting input element.

Simply import Field from React Proforma:

import { Field } from 'react-proforma';

Example:

<Field
  name="password"
  type="password"
  className="form-field"
  id="password-field"
  style={{ color: 'blue', fontSize: '1.3rem' }}
/>
<Field
  name="amount"
  type="number"
  className="form-field"
  id="amount-field"
  style={{ fontWeight: 'bold' }}
/>
  • Field.name: string (required)
    • A string value corresponding with one of the keys on your 'initialValues' object in your Proforma.config.
  • Field.type: string (optional -- defaults to "text")
    • The type of input you'd like the resulting input element to be.
  • Field.component: React.ComponentType (optional)
    • A custom component you'd like to use instead of having a standard input element returned from Field. See here for how to use this.
  • Field...otherProps
    • All other props will be forwarded to the resulting input or custom component.
  • Field.customOnChange: function (CAUTION: RARELY NEDED)
    • This is an escape-hatch prop that allows you to pass in a function that will,when that field is changed, be executed in place of Proforma's internal change handler.
    • I built this in because I came across some libraries that do not conform to React standards, and so require more meticulous change handling. It's here if you need it.
  • Field.customValue: string | boolean (CAUTION: RARELY NEEDED)
    • This is another escape-hatch prop that allows you to control the "value" of this input entirely yourself.
    • I built this in because I came across some libraries that do not conform to React standards, and so require more meticulous value management. It's here if you need it.

Checkbox

This component is for checkbox inputs. It comes automatically wired up as a controlled component.

The Checkbox component must be passed a 'name' prop corresponding with one of the form field names in your 'initialValues' object in your Proforma.config. All other props will be forwarded to the resulting input element.

NOTE: The 'initialValue' for a Checkbox input must be a BOOLEAN value, not a string.

Simply import Checkbox from React Proforma:

import { Checkbox } from 'react-proforma';

Example

<Checkbox name="newsletter" id="newsletter-checkbox" />
  • Checkbox.name: string (required)
    • A string value corresponding with one of the keys on your 'initialValues' object in your Proforma.config.
  • Checkbox.component: React.ComponentType (optional)
    • A custom component you'd like to use instead of having a standard input element returned from Checkbox. See here for how to use this.
  • Checkbox...otherProps
    • All other props will be forwarded to the resulting input or custom component.

Radio

This component is for radio button inputs. It comes automatically wired up as a controlled component.

The Radio component must be passed a 'name' prop corresponding with one of the form field names in your 'initialValues' object in your Proforma.config. All other props will be forwarded to the resulting input element.

Simply import Radio from React Proforma:

import { Radio } from 'react-proforma';

Example

<label htmlFor="gender-male">Male</label>
<Radio name="gender" value="male" id="gender-male" />

<label htmlFor="gender-female">Female</label>
<Radio name="gender" value="female" id="gender-male" />

<label htmlFor="gender-other">Other</label>
<Radio name="gender" value="other" id="gender-other" />

NOTE: Each radio button above has the same 'name' prop ("gender" in this case). This is required for the radio buttons to be considered part of the same radio grouping.

NOTE: Each radio button above has it's own 'value' prop. This make it different from other input types. The 'value' prop should be equal to the value that you'd like your form field name to have when that radio button is checked.

  • Radio.name: string (required)
    • A string value corresponding with one of the keys on your 'initialValues' object in your Proforma.config.
  • Radio.value: string (required)
    • A string value representing the 'value' that your form field name will have when that particular radio button is checked.
  • Radio.component: React.ComponentType (optional)
    • A custom component you'd like to use instead of having a standard input element returned from Radio. See here for how to use this.
  • Radio...otherProps
    • All other props will be forwarded to the resulting input or custom component.

Select

This component is for select (dropdown) inputs. It comes automatically wired up as a controlled component.

The Select component must be passed a 'name' prop corresponding with one of the form field names in your 'initialValues' object in your Proforma.config. All other props will be forwarded to the resulting select element.

Simply import Select from React Proforma:

import { Select } from 'react-proforma';

Example

<Select name="salutation" id="salutation-select" className="form-field">
  <option value="mister">Mr.</option>
  <option value="missus">Mrs.</option>
  <option value="miss">Ms.</option>
</Select>
  • Select.name: string (required)
    • A string value corresponding with one of the keys on your 'initialValues' object in your Proforma.config.
  • Select.component: React.ComponentType (optional)
    • A custom component you'd like to use instead of having a standard select element returned from Select. See here for how to use this.
  • Select...otherProps
    • All other props will be forwarded to the resulting select or custom component.

Textarea

This component is for textarea inputs. It comes automatically wired up as a controlled component.

The Textarea component must be passed a 'name' prop corresponding with one of the form field names in your 'initialValues' object in your Proforma.config. All other props will be forwarded to the resulting textarea element.

Simply import Textarea from React Proforma:

import { Textarea } from 'react-proforma';

Example

<Textarea
  name="comment"
  id="comment-input"
  cols="50"
  rows="10"
  maxLength="120"
/>
  • Textarea.name: string (required)
    • A string value corresponding with one of the keys on your 'initialValues' object in your Proforma.config.
  • Textarea.component: React.ComponentType (optional)
    • A custom component you'd like to use instead of having a standard textarea element returned from Textarea. See here for how to use this.
  • Textarea...otherProps
    • All other props will be forwarded to the resulting textarea or custom component.

Submit

This component is meant to be the control for your form submission. It comes automatically wired up to the ProformaBundle's handleSubmit method and 'isSubmitting' property.

As customization, it accepts 'textNotSubmitting' and 'textSubmitting' as optional props. See props section below for details.

Simply import Submit from React Proforma:

import { Submit } from 'react-proforma';

Example

<Submit textSubmitting="Processing form inputs..." />
  • Submit.textNotSubmitting: string (optional)
    • This is the text that will be displayed when the form is not currently being submitted.
    • Defaults to 'Submit'.
  • Submit.textSubmitting
    • This is the text that will be displayed when the form is currently being submitted.
    • Defaults to 'Submitting...'.
  • Submit.component: React.ComponentType (optional)
    • A custom component you'd like to use instead of having a standard button element returned from Submit. See here for how to use this.
  • Submit...otherProps
    • All other props will be forwarded to the resulting button or custom component.

Reset

This component is meant to be the control for your form's reset functionality. It comes automatically wired up to the ProformaBundle's handleReset method.

As customization, it accepts 'text' as an optional prop. See props section below for details.

Simply import Reset from React Proforma:

import { Reset } from 'react-proforma';

Example

<Reset text="Click To Reset" />
  • Reset.text: string (optional)
    • This is the text that will be displayed to the user.
    • Defaults to 'Reset'.
  • Reset.component: React.ComponentType (optional)
    • A custom component you'd like to use instead of having a standard button element returned from Reset. See here for how to use this.
  • Reset...otherProps
    • All other props will be forwarded to the resulting button or custom component.

Debug

This component is designed for development purposes for you to display and track the current 'values', 'errors', and 'touched' objects for your form.

See what it looks like inside the 'Normal Usage' guide.

Import it,

import { Debug } from 'react-proforma';

and insert it anywhere within your markup:

<Debug />

ProformaBundle

  • ProformaBundle.values: object

    • An object whose keys are the form field names you passed in to Proforma in your 'initialValues' object, and whose values are the current field values for each field name.
  • ProformaBundle.touched: object

    • An object whose keys are the form field names you passed in to Proforma in your 'initialValues' object, and whose values are the current 'touched' state, represented by a boolean value, for each field name.
  • ProformaBundle.errors: object

    • An object whose keys are the form field names you passed in to Proforma in your 'initialValues' object, and whose values are the current errors for that field, based on the current value in that field and the 'validationObject' you passed into Proforma.
    • If there are errors, they will be present as an array of strings.
    • If there are no errors, the value of that field name's errors will be null.
  • ProformaBundle.isSubmitting: boolean

    • A boolean flag representing whether or not the form is currently submitting.
    • Useful for tailoring your UI in order to inform that user of the form's current submission status.
    • Controlled by ProformaBundle.setSubmitting.
  • ProformaBundle.isComplete: boolean

    • A boolean flag representing whether or not the form processing is complete.
    • Starts off as false, and is only turned true by you with a call to ProformaBundle.setComplete(true).
    • There aren't too many cases where you will need this. The only thing I can think of is if you want to keep your form on the screen for some reason after the form has been processed, and you want to tell the user that the form has been successfully processed.
  • ProformaBundle.submitCount: number

    • A value representing the number of times your form has been submitted. Starts at zero, and increments by 1 on each call to ProformaBundle.handleSubmit from your form.
  • ProformaBundle.handleChange: function

    • This is Proforma's form input change handler.
    • Use it to manually hook up a form field to the rest of Proforma's functionality.
    • NOTE: The input you're attaching 'ProformaBundle.handleChange' to MUST have a 'name' prop passed to it that corresponds with one of your form field names in your 'Proforma.config.intialValues' object.
    • Only needed if you are not using any of Proforma's own custom components, or if you are facing some very special edge-case.
  • ProformaBundle.handleFocus: function

    • This is Proforma's form input focus handler.
    • Use it to manually hook up a form field to the rest of Proforma's functionality.
    • NOTE: The input you're attaching 'ProformaBundle.handleFocus' to MUST have a 'name' prop passed to it that corresponds with one of your form field names in your 'Proforma.config.intialValues' object.
    • Only needed if you are not using any of Proforma's own custom components, or if you are facing some very special edge-case.
  • ProformaBundle.handleBlur: function

    • This is Proforma's form input focus handler.
    • Use it to manually hook up a form field to the rest of Proforma's functionality.
    • NOTE: The input you're attaching 'ProformaBundle.handleBlur' to MUST have a 'name' prop passed to it that corresponds with one of your form field names in your 'Proforma.config.intialValues' object.
    • Only needed if you are not using any of Proforma's own custom components, or if you are facing some very special edge-case.
  • ProformaBundle.handleSubmit: function

    • This is Proforma's form submission handler.
    • Among other things, this is where Proforma executes your 'handleSubmit' function that you passed to Proforma.
    • See Proforma for more information about the 'handleSubmit' function that you pass to Proforma.
  • ProformaBundle.handleReset: function

    • This is Proforma's form reset method.
    • Invoking this method will reset all of your fields back to the initial values defined by your 'initialValues' object passed to 'Proforma.config', as well as resetting the properties on the 'errors' and 'touched' objects back to null and false respectively.
    • Attach it to an element's onClick property, or import the Reset component, which comes pre-wired up with 'ProformaBundle.handleReset'.
  • ProformaBundle.setSubmitting: function

    • Use this function to control the value of 'ProformaBundle.isSubmitting'. (See above).
  • ProformaBundle.setComplete: function

    • Use this function to control the value of 'ProformaBundle.isComplete'. (See above).

Helper functions

fieldValidator

  • A function that exposes an internal class that allows you to easily construct your collection of error messages for a given form field.
  • Accepts a single argument: the value you wish to validate.
    • I.e. values.name, values.password, values.email, etc.
  • Used inside your 'Proforma.config.validationObject'.
  • NOTE: Remember to return the fieldValidator chain from the validation function.
  • NOTE: Remember to add .end() to the end of your chain!
  • Each chained method accepts an optional argument where you can customize the error message for that part of the validation. See the list of available methods below for their exact function signatures.
  • Imported from React Proforma:
import { fieldValidator } from 'react-proforma';
  • Example usage:
validationObject: {
  userName: (values) => {
    return fieldValidator(values.userName)
      .required()
      .min(6)
      .max(20)
      .end();
  },
  email: (values) => {
    return fieldValidator(values.email)
      .required()
      .email()
      .end();
  }
}
  • List of methods available for chaining on to fieldValidator(value):
    • required(msg?: string)
      • Requires that this field must be be filled in (i.e. length greater than zero).
      • Default message: 'This field is required.'
    • min(length: number, msg?: string)
      • Sets a minimum number of characters for that field.
      • Default message: 'This field must be at least [length] character(s) long.'
    • max(length: number, msg?: string)
      • Sets a maximum number of characters for that field.
      • Default message: 'This field must not be more than [length] character(s) long.'
    • integer(msg?: string)
      • Requires that this field's value must be an integer.
      • Default message: 'The value entered must be an integer.'
    • float(msg?: string)
      • Requires that this field's value must be a floating point value.
      • Default message: 'The value entered must be a floating point number.'
    • email(msg?: string, rgx?: RegExp)
      • Requires that this field must be a valid email address.
      • Default message: 'Please enter a valid email address.'
      • Default regex: /^(([^<>()[]\.,;:\s@"]+(.[^<>()[]\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/
    • regex(rgx: RegExp, msg? string)
      • Accepts a regular expression against which to test the value.
      • Default message: 'The value entered does not match the required regular expression pattern.'
    • equals(comparedString: string, msg?: string)
      • Accepts a string value against which the field value is to be compared.
      • Default message: 'The value in this field does not equal "[comparedString]".'
    • custom(fn: () => string | undefined)
      • Accepts a single function that should return a string value or undefined. If a string value is returned, it will be added to the current collection of error messages.
      • There is no default message, as your desired error message is to be returned from your passed-in function.
    • end()
      • YOU MUST ADD .end() TO THE END OF ANY CHAIN of methods attached to fieldValidator in order for the chain to return the collection of error messages.
      • This will always be the last method in your method chain.

fieldError

  • This is a function that you can insert anywhere into your form mark-up (under each form field for example) that will help you display the error messages for a specific field.
  • Expects two arguments:
    1. name: string (required) -- the 'name' of the form field whose errors you wish to display (i.e. a name corresponding with one of the properties on your 'initialValues' object).
    2. component: React.ComponentType (required) -- the component of your own design that will be passed each individual error message as its only prop called "error".
  • Import it from React Proforma:
import { fieldError } from 'react-proforma';
  • Example usage:
/* example error display component */
function FieldError(props) {
  return <p style={{ color: 'red' }}>{props.error}</p>;
}

/* then insert the fieldError call somewhere in your mark-up */
<div className="field-row">
  <label htmlFor="password-field">Enter password:</label>
  <Field name="password" type="password" id="password-field"/>
</div>
{fieldError('password', FieldError)}

fieldValid

  • This is a function similar to 'fieldError', except that it is meant to display a message to the user if a field's value is valid.
  • Expects three arguments:
    1. name: string (required) -- the 'name' of the form field whose errors you wish to display (i.e. a name corresponding with one of the properties on your 'initialValues' object).
    2. component: React.ComponentType (required) -- the component of your own design that will be passed each individual error message as its only prop called "message".
    3. message: string (required) -- the message you wish to display (e.g. 'Good!').
  • Import it from React Proforma:
import { fieldValid } from 'react-proforma';
  • Example usage:
/* example valid-input display component */
function FieldValid(props) {
  return <p style={{ color: 'green' }}>{props.error}</p>;
}

/* then insert the fieldValid call somewhere in your mark-up */
<div className="field-row">
  <label htmlFor="password-field">Enter password:</label>
  <Field name="password" type="password" id="password-field"/>
</div>
{fieldValid('password', FieldValid, 'Good stuff!')}

NOTE: 'fieldError' and 'fieldValid' can be used in conjunction with each other. Just place them one above the other, and either error messages or the valid input message will be displayed, depending on the current input in that field:

{fieldError('password', FieldError)}
{fieldValid('password', FieldValid, 'Good stuff!')}

Running tests

  1. Clone the Github repo:

    git clone https://github.com/varunj166/react-proforma.git

  2. Install dependencies

    npm install

  3. Run test suites

    npm test


What about React Native?

I've only looked at React Native in passing because web applications are my main focus (and are, in my opinion, the future, especially with the possibilities that PWA's make available to us), but if there is sufficient interest in this library, I could see about enlisting some help to make React Proforma compatible with React Native.

As I understand it, it shouldn't be that difficult, but I wouldn't want to introduce new functionality like that myself without the help of people with much stronger knowledge of React Native.

For now, React Proforma is strictly for React web forms.


What About Formik?

I am, of course, aware of Formik, seeing as it's the most popular React form helper library out there. And I did work with Formik a bit, but there were design choices there that annoyed me, and for something as frequently needed as a form helper library, I didn't want to have to conform to designs that I didn't agree with.

That's what inspired me to build React Proforma. Everything about it is exactly how I would have wanted someone else's form helper library to be. And since that wasn't out there, I made it myself.

It works really well for me, and I hope it works well for you, too!

-vJ