Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
form4react
Advanced tools
Abstract form handling for any purpose (< 2 kb gzipped)
The following statements represent only my personal opinion, although I did not work a lot with the following pretty good packages.
redux-form:
A lot of features, nice documented and easy to use
form handling tool, but it comes with the cost of a little integrative work,
the cost of big package size, forces you to hold the form data in the redux store
and to connect every form and has poor flow
assistance.
react-final-form:
A lot of features, optimized for performance,
small bundle size, totally customizable and needs no integrative work,
but is not as well documented and has no flow
assistance.
flow
assistanceyarn add form4react
npm install --save form4react
First take a look at the outer form element.
import { Form } from 'form4react';
const render = () => (
<Form validation={validation}
onChange={onChange}
data={data}
onSubmit={onSubmit}>
{/* form content */}
</Form>
);
It expects the following props: (See below for the missing Flow types)
Props | Type | Description | Example |
---|---|---|---|
className | string | Will be applied to the form tag | 'my-form' |
validation | FormValidation<V> | Contains all validators | { name: { onChange: customValidator } } |
data | FormData<V> | Contains all values, errors and submitting state | { values: { name: 'Scotty' }, errors: { name: { id: 'does.not.know' } }, submitting: false } |
onChange | (FormData<V>) => void | Handles the next data after any changes have been made | |
onSubmit | V => void \ Promise<void> | Will be called if submitted without any failing validators | |
onSubmitFailed | void => void | Will be called if submitting was aborted due to validation errors | |
onSubmitFinished | void => void | Will be called if after submitting finished |
Now lets take a look on a single integrated form element:
import { Form, Field } from 'form4react';
const render = () => (
<Form validation={validation}
onChange={onChange}
data={data}
onSubmit={onSubmit}>
<Field name="phone" >
{({ onChange, onBlur, required }) => (
<div>
<label>{'Phone' + required ? '*' : ''}</label>
<input value={data.values.phone} onChange={onChange} onBlur={onBlur} />
<span className="error">{__(data.errors.phone)}</span>
</div>
)}
</Field>
</Form>
);
By using the Field
which excepts only one property name
of type string,
the rendered children get access to the three attributes:
onChange
: Receives the next value, runs the onChange-Validator on it
and calls the Form
- onChange
where the data.values.phone
holds the
updated value and data.errors.phone
will maybe hold an occurred validation error.
onBlur
: Same as the onChange
but will be triggered if the input loses
the users focus.
required
: A boolean which indicates if the value undefined
for "phone"
would pass all validations on that field. If not the value is treated as
required.
The above example shows a Form
with a single Field
.
The label shows "Phone*" if the value is required.
It will update the form data accordingly to the specified onChange
handler
supplied to the Form.
It will display an error message below the input if any error is encountered.
To implement async form field validation, you simply have to return a promise, which will resolve the validation result. You can write validators which are both: async AND sync validator at the same time. One example:
const specialValidator = (val: any) => {
if (!val) { // if value is falsy we return sync error
return {id: 'value.required'};
}
if (typeof val !== 'number') { // only check non numbers
return myFetch('/my/api').then(result => {
if (result.isInvalid) { // api answered that the value was invalid
return {id: 'value.invalid'}; // return async error
}
})
}
}
HINT: You should always validate the value undefined
synchronously, because
form4react
determines the requirements by these criteria. So if you use the preferable
async-await
-Syntax, you should split your validator into to parts.
When the user submits the form, the following will happen:
submitting
property will be propagated as true
to your onChange
2
returned any errors your onChange
will be called with the found errors and submitting
as false
(Early exit)2
returned no errors your onSubmit
will be called4
took place, the submitting
property will be propagated as false
(if your onSubmit
returned a Promise, this will happen after this Promise resolved)form4react comes with a lot of types and exports FormData<T>
, ErrorMessage
and Validator
.
In this table you get an overview of relevant types.
Name | Flow declaration | Information |
---|---|---|
ErrorMessage | { id: string, values?: {[string]: mixed}} | This structure allows to handle internationalization by transporting the required information like the intl key and placeholder values |
FormData<V> | { values: V, errors: { [$Keys<V>]: void \ ErrorMessage } } | This is the main structure for the data represent the form state |
Validator | any => ErrorMessage \ void \ Promise<ErrorMessage \ void> | The validator returns void if no error occurred or a Promise if the validation is asynchronous |
ValidationType | 'onChange' \ 'onBlur' \ 'onSubmit' | Currently only those to validation types can be specified |
FormValidation<V> | { [$Keys<V>]: { [ValidationType]: Validator \ void } } | For each key of the specified form values V here can be specified all validations for this field |
In this chapter we will examine how a sample form could be implemented. The sample is hosted be GitHub Pages.
We first define the types of the form values...
type MyFormValues = {
gender: string,
firstName: string,
lastName?: string,
age: number,
};
// gender can be selected with the following available options
const genderOptions = [{ value: 'M', label: 'male' }, { value: 'F', label: 'female' }];
Then we define the validations... (see here for the used validators below)
const validation = {
firstName: { onChange: Validators.string({ min: 1, max: 10 }) },
lastName: { onBlur: Validators.string({ min: 1, max: 10 }) },
age: { onChange: Validators.number({ minLength: 1, maxLength: 3 }) },
};
Let's say we decide to control the form state inside our app state...
type AppState = { data: FormData<MyFormValues> };
Now we define the first version of the app... (see here for all used custom components below)
import React, { Component } from 'react';
import { Form } from 'form4react';
import type { FormData } from 'form4react';
class App extends Component<{}, AppState> {
// first we initialize the defaults (if we would not, flow would complain for all mandatory fields missing)
// we could also start with initial errors, but here we don't
state: AppState = { data: { values: { gender: 'M', firstName: 'Nick', age: 21 }, errors: {} } };
// this handler will propagate the form changes to the state
onChange = (data: FormData<MyFormValues>) => this.setState({ data });
// for now we leave the submit handler empty
onSubmit = ({ gender, firstName, lastName, age }: MyFormValues): void => {
// do nothing
};
render(): React$Node {
const data = this.state.data;
const { values, errors } = data;
return (
<Form validation={validation} onChange={this.onChange} data={data} onSubmit={this.onSubmit}>
<div className="row">
<FormInput
name="firstName"
value={values.firstName}
error={errors.firstName}
label="First name"
className="form-group col-sm-6"
/>
<FormInput
name="lastName"
value={values.lastName}
error={errors.lastName}
label="Last name"
className="form-group col-sm-6"
/>
</div>
<div className="row">
<FormSelect
name="gender"
value={values.gender}
error={errors.gender}
options={genderOptions}
label="Gender"
className="form-group col-sm-6"
/>
<FormNumberInput
name="age"
value={values.age}
error={errors.age}
label="Age"
className="form-group col-sm-6"
/>
</div>
<button className="btn btn-primary">Submit</button>
</Form>
);
}
}
This version does the specified validations on the fields and prevents our empty
onSubmit
function from being called, when any validations would not succeed
So now we add a table (see here
for the implementation and the new type Person
) to hold all entered person data. What changes...
// the state interface
type AppState = {
data: FormData<MyFormValues>,
persons: Person[],
};
// the initial state
state: AppState = { data: { values: { gender: 'M', firstName: 'Nick', age: 21 }, errors: {} }, persons: [] };
// we add below the form the table
<PersonTable persons={this.state.persons} />
// we implement functionality for the onSubmit
onSubmit = ({ gender, firstName, lastName = '', age }: MyFormValues): void => {
this.setState({
persons: [...this.state.persons, { gender: gender === 'M' ? 'M' : 'F', firstName, lastName, age }],
});
};
Now each time we submit the entered data which succeeds all validations, a new
person entry will be displayed in the PersonTable
.
Finally we will add a clear button for all inputs. What changes...
// a new function inside our App
onClear = () => {
this.setState({ data: { values: { gender: 'M', firstName: '', age: 0 }, errors: {} } });
};
// the submit button on the bottom of the form will be replaced by
<div className="btn-toolbar" role="toolbar">
<button className="btn btn-secondary" type="button" onClick={this.onClear}>
Clear
</button>
<button className="btn btn-primary" disabled={Form.hasErrors(data)}>
Submit
</button>
</div>
Now if we hit the clear button all entered values will be cleared.
What about asynchronous form error handling?
Since you are the owner and controller of any updates on the form data, you can simply add new errors for any fields after the server responded with any error. If you want to write a util which maps specific server errors to specific fields, you have the ability to do so.
Why are there no ready-to-use components exported like FormSelect
from the above examples?
form4react is primarily made to handle updates, validations, storing of data, save the developer by good flow assistance and as a guideline for form handling
Later there might be subdirectories which you can optionally use, but most often in larger projects you want to have full control over all form components.
FAQs
Abstract form handling for any purpose (1 kb gzipped)
The npm package form4react receives a total of 0 weekly downloads. As such, form4react popularity was classified as not popular.
We found that form4react demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.