README
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
- Installation
- Basic Usage
- Normal Usage
- Typescript
- Going further
- API
- Running tests
- What about React Native?
- What About Formik?
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 theProforma
component, and inside that is a reference to a function that I've calledrenderForm
(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 nameproformaBundle
. - Please note that there are no parentheses next to
this.renderForm
! This is a function reference, not a function invocation.
config
and handleSubmit
props to the Proforma
component
Step 2: Add 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 yourhandleSubmit
function on execution.- There will be nothing on your screen because
renderForm
isn't returning anything. Let's fix that next.
renderForm
Step 3: Fill in 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
, andhandleBlur
fromproformaBundle
.- See the ProformaBundle API for a full breakdown of all the properties and methods you can access from inside
proformaBundle
.
- See the ProformaBundle API for a full breakdown of all the properties and methods you can access from inside
renderForm
then returns a regular React form element.- I manually assign the various handlers to their respective event hooks in the
form
andinput
elements, set thename
prop on theinput
element to be 'field1', and hook up thevalue
prop of the input tovalues.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).
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.)
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. Insideconfig
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.
- Again we have our
- Looking inside
renderForm
:Form
,Field
, andSubmit
- Here is our first exposure to the
Form
,Field
, andSubmit
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 aname
(that corresponds with one of the keys on yourinitialValues
object) and atype
(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.)
- Here is our first exposure to the
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 theconfig
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 ornull
.- In this example, I destructure the 'name' value from
values
, set up an empty errors array, and then run two validation tests on 'name':- Make sure the length of 'name' is not zero (i.e. this field is required)
- 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:
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:
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:
import { fieldError } from 'react-proforma';
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>; }
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:
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:
The
Proforma
component is a generic class, so you just have to pass in thetype
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!
In your
renderForm
method (or whatever function returns your form mark-up), in order to have yourproformaBundle
argument fully typed, you have to importProformaBundle
, which is a generic type that expects yourFormValues
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:
- 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';
- 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;
`;
- 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.
Proforma.config.customOnChange
Using - 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.
- 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:- event: React.ChangeEvent -- the change event emitted by React.
- 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 usesetValues
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
.
- A simple boolean flag that, if true, resets the 'touched' value of each field name back to
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 ownhandleSubmit
. - 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.
- NOTE: If your
- 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:- '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']).
- 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 ishandleReset
in the ProformaBundle), andsubmitCount
. 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 theevent.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 fromForm
. See here for how to use this.
- A custom component you'd like to use instead of having a standard
- Form...otherProps
- Any additional props passed to
Form
will be forwarded onto the resulting Reactform
element.
- Any additional props passed to
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.
- The type of input you'd like the resulting
- Field.component: React.ComponentType (optional)
- A custom component you'd like to use instead of having a standard
input
element returned fromField
. See here for how to use this.
- A custom component you'd like to use instead of having a standard
- Field...otherProps
- All other props will be forwarded to the resulting
input
or custom component.
- All other props will be forwarded to the resulting
- 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 fromCheckbox
. See here for how to use this.
- A custom component you'd like to use instead of having a standard
- Checkbox...otherProps
- All other props will be forwarded to the resulting
input
or custom component.
- All other props will be forwarded to the resulting
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 fromRadio
. See here for how to use this.
- A custom component you'd like to use instead of having a standard
- Radio...otherProps
- All other props will be forwarded to the resulting
input
or custom component.
- All other props will be forwarded to the resulting
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 fromSelect
. See here for how to use this.
- A custom component you'd like to use instead of having a standard
- Select...otherProps
- All other props will be forwarded to the resulting
select
or custom component.
- All other props will be forwarded to the resulting
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 fromTextarea
. See here for how to use this.
- A custom component you'd like to use instead of having a standard
- Textarea...otherProps
- All other props will be forwarded to the resulting
textarea
or custom component.
- All other props will be forwarded to the resulting
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 fromSubmit
. See here for how to use this.
- A custom component you'd like to use instead of having a standard
- Submit...otherProps
- All other props will be forwarded to the resulting
button
or custom component.
- All other props will be forwarded to the resulting
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 fromReset
. See here for how to use this.
- A custom component you'd like to use instead of having a standard
- Reset...otherProps
- All other props will be forwarded to the resulting
button
or custom component.
- All other props will be forwarded to the resulting
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 turnedtrue
by you with a call toProformaBundle.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.
- A value representing the number of times your form has been submitted. Starts at zero, and increments by 1 on each call to
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
andfalse
respectively. - Attach it to an element's
onClick
property, or import theReset
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 tofieldValidator
in order for the chain to return the collection of error messages. - This will always be the last method in your method chain.
- YOU MUST ADD
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:
- 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).
- 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:
- 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).
- component: React.ComponentType (required) -- the component of your own design that will be passed each individual error message as its only prop called "message".
- 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
Clone the Github repo:
git clone https://github.com/varunj166/react-proforma.git
Install dependencies
npm install
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