React cross Form
Easy form for react and react-native apps with validation.
We use the great validate.js library but you can use a custom validator.
Optional - You can use react-cross-inputs , Example with react cross inputs
This is only a logic component, react-cross-form just render your inputs with value, methods, validators.
Installation
npm i react-cross-form --save
Basic Usage
import React from "react";
import DocForm from "react-cross-form";
import TextInput from "./TextInput"; // You can use any component you want.
const FORM_FIELDS = [
{
key: "firstName",
label: "First Name",
placeholder: "Type your name...",
component: TextInput,
validators: { presence: { message: "is required" }, length: { minimum: 3 } }
},
{
key: "email",
label: "Email",
placeholder: "Type your name...",
component: TextInput,
validators: { email: true }
}
];
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
form: {
firstName: null,
email: null
},
isValid: false
};
this.onChange = this.onChange.bind(this)
}
onChange({updateData}){
this.setState({ form: updateData })
}
render() {
const { form, isFormValid } = this.state;
return (
<div>
<h2>Easy form validation example</h2>
<form>
<DocForm
fields={FORM_FIELDS}
data={this.state.form}
onChange={this.onChange}
validateType="all"
onValidateStateChanged={({ isValid }) => {
this.setState({ isFormValid: isValid });
}}
/>
</form>
</div>
);
}
};
Props
key | type | Description |
---|---|---|
data | object (required) |
Document data |
fields | array (required) |
fileds to render. [{ key : 'age', component : NumberInput, label : 'Your Age', validators: { presence: true }: // And you can add here everything you need and your input will get this from props }] |
validateType | string (default: 'all') |
Choose When to show validation errors in each of the inputs fields? none - hide all. all - Show all onFocus - Show only on fields that then been focus onBlur - how only on fields that then been blur onChange- how only on fields that then been changed. |
showSkippingFieldsWarnings | boolean | Set true if you want to display validation error even if user skip on the input |
onFocus | function | Function that get called When input is focus, you need to call props.onFocus from your input |
onBlur | function | Function that get called When input is blur, you need to call porps.onBlur from your input |
onChange | function (required) |
The function that gets called When the input is changed, you need to call props.onChange and pass the new value from your input. See Input event for more details. |
onValidateStateChanged | function | Function that get called when the form validation state was changed, With this value you can enable/disable the form submit button |
requiredPrefix | string (Default is * ) |
You can use this to render input label with a required fields labels will render with '*' at the start of the string |
disabledAll | boolean | Set true if you want to disable all fields |
focusNextOnlyIfEmpty | boolean | Set true if you want to focus on the next input only if is empty |
render | fonction | if you want to customize the layout, for example, you want to wrapper any input with other element or put between the inputs some other element, you can use render props function that will get an object with all the inputs and return the view to render |
onChanged | function | This function will run after field onChange, helpful for manipulating other field onChange, see onChanged example |
Fields
Each field in the Fields array is an object that must to contain a key and component.
more info about field object:
key | type | Description |
---|---|---|
key | string(required) | Key of for the value in the data |
component | element(required) | Any component that you want to render, see what your componant will get Component props |
formatter | function | If you want to format the value, you can pass a function the received ({field, documentData}) and return the input value |
validators | object | For example: { presence: true, email: true }, you can learn more in https://validatejs.org/#validators and see More validate examples) |
customValidation | function | Pass function that get ()field, value and return array of validation errros, see CustomValidation exmple |
disabled | boolean | Input disabled value |
placeholder | string | Input placeholder value |
label | string | Input label value |
helpText | string | Sometimes we want to render help text under the field |
render | function | You can pass render method that return a component |
Fields configuration example
const FIELDS = [
{
key: 'firstName',
label: i18n.t('First Name'),
component: TextInput,
validators: { presence: true, length: { minimum: 2 }},
autoFocus: true
},
{
key: 'options',
label: 'Options',
render: (props) => {
return <TextInput {...props} />
},
}
]
<DocForm fields={FIELDS}/>
Input props
This exmple with all this props that input will get from react-cross-form
import React from 'react';
export default class MyInput extends React.Component {
constructor(props) {
super(props);
}
render() {
const {
id, // the field.key
value, // input value
onFocus, // function
onChange, // function
onBlur, // function
isValid, // boolean
showWarnings, // boolean
validatorMessage, // array of errors
required, // boolean
placeholder, // string
label, // string,
requiredPrefix, // string
labelWithPrefix, //string
disabled, // boolean
autoFocus, // boolean
requiredPrefix, // string
onRef, // function
focusNext // function (Run to focus on the next input)
getOtherFieldRefByKey// function that return other field ref by the field key
// and all the rest from your field config
} = this.props;
return (
<div>
<label> {labelWithPrefix} </label>
<input
ref={ref => {
onRef(ref);
}}
value={value}
onFocus={onFocus}
onChange={e => onChange(e.target.value)}
onBlur={onBlur}
disabled={disabled}
/>
{showWarnings && <p>{validatorMessage}</p>}
</div>
);
}
}
FocusNext
Your input will get focusNext funciton from props.
react native - use inside onSubmitEditing
<TextInput
onSubmitEditing={this.props.focusNext}
validators
We recommend you to work with validate.js guide to build your validators object.
but if you want, you can use our helper that can help you to build the object but he didn't include all the options
buildValidateJsObject
import Form, {buildValidateJsObject} from 'react-cross-form';
const validtors = buildValidateJsObject({
required: true,
min: 3,
max:6,
isEmail: false,
isNumber: true,
isUrl: false,
isDate: false,
earliest: null,
latest: null
})
/*
The result will be-
{
"presence":true,
"numericality:{
"greaterThan":3,
"lessThan":6
}
}
*/
validators examples
// Email
{ email: true }
// Length
{length: {is: 3}}
{length: {minimum: 20}}
{length: {maximum: 3}}
// Numbers
{numericality: {noStrings: true}}
// Reuired
{presence: true}
// Custom message
{presence: {message: "is required"}}
You can learn more with validatejs docs: https://validatejs.org/#validator
Input event
There are 3 events that will pass to your inputs and you handle from the parent:
event | input side(event) | parent side(callback) |
---|---|---|
onFocus | Just run the funtion , you didn't need to pass any value | Your callback will get {key, value, isValid, initialValue, info} |
onChange | onChange(value) | Your callback will get {key, value, isValid, initialValue, updateData, info} |
onBlur | Just run the funtion , you didn't need to pass any value | Your callback will get {key, value, isValid, initialValue, updateData, info} |
info- you can pass to component parent any parameters you need with this events callbacs.
const info = {someKey: '123'}
// When you call onChange, info is the second parameters
// When you call onBlur or onFocus the info is the first one
<input
onFocus={e => this.props.onChange(info)}
onChange={value => this.props.onChange(value, info)}
onBlue={e => this.props.onChange(info)}
Parent events call backs
The parent will get an object with relevant data from the input event
{key, updateData , initialValue , isValid , info}**
- key - this value from the Field object
- updateData (only when onChange is called)
- initialValue - this is the inital value of your field from componentDidmount
- isValid - boolean value base of your validators from Field object
- value - this is the new value from your input
*when you call the onChange() from your input the first parameter is the new value
- info- all other info that your input will be passing thru the event call back
// Example
return (
<DocForm
data={this.state.form}
fields={FORM_FIELDS}
onChange={({key, value, updateData}) => {
let form = {...this.state.form, [key]: value}
this.setState({form})
// or just use the updateData:
// this.setState({form: updateData})
}}
onBlur={res => {console.log(res.key, ' is blue')}}
onFocus={res => {console.log(res.key, ' is focus')}}
/>
)
customValidation exmple
In this example we use libphonenumber-js to validate a phone number.
this customValidation need to be a function that get (field, value) and return an array of strings errors
import { isValidNumber } from 'libphonenumber-js'
const formFields = [
{
key: 'mobile',
label: i18n.t('Mobile'),
component: MobileInput,
customValidation : function (field, value, data, formProps){
let errors = []
if(value === ''){
errors.push('can\'t be blank')
} else if(!isValidNumber(value)){
errors.push('Please enter a valid phone number')
}
return errors
}}
]
Example with react cross inputs
import { isValidNumber } from 'libphonenumber-js'
import {TextInput, NumberInput, MobileInput, UploadFile, DateTime, Pointer, DropDown, ObjectsDropDown} from 'react-cross-inputs';
const DocFields = [
{
key: 'firstName',
label: 'First Name',
validators: { presence: true, length: { minimum: 2 } },
component: TextInput,
},
{
key: 'lastName',
label: 'Last Name',
validators: { presence: true, length: { minimum: 2 } },
component: TextInput,
},
{
key: 'mobile',
label: 'Mobile',
customValidation: function (field, value) {
let errors = []
if(!value || value === '') {
errors.push('can\'t be blank')
} else if(!isValidNumber(value)) {
errors.push('Please enter a valid phone number')
}
return errors
},
component: MobileInput,
},
{
key: 'image',
label: 'Image',
component: UploadFile,
validators: { presence: true }
},
{
key: 'age',
label: 'Age',
component: NumberInput,
},
{
key: 'birthday',
label: 'Birthday',
component: DateTime,
},
{
key: 'city',
label: 'City',
component: Pointer,
schemaName: 'City',
targetName: 'CityDropDown',
labelKey: 'name'
},
{
key: 'favoriteColor',
label: 'Favorite Color',
component: DropDown,
options: [
{key: 'red', label: 'red'},
{key: 'green', label: 'green'},
{key: 'blue', label: 'blue'},
{key: 'yellow', label: 'yellow'},
{key: 'pink', label: 'pink', customRender: ({label}) => <p style={{color: 'pink'}}>{label}</p>},
]
},
{
key: 'favoritePhone',
label: 'Favorite Phone',
component: ObjectsDropDown,
valueKey: 'model', // default is key
labelKey: 'fullName', // default is label
options: [
{company: 'Samsung', OS: 'Android 8', model: 'Galaxy S9 Plus', fullName: 'Samsung Galaxy S9 Plus'},
{company: 'Samsung', OS: 'Android 8', model: 'Galaxy S9', fullName: 'Samsung Galaxy S9'},
{company: 'Huawei', OS: 'Android 8.1', model: 'P20 Pro', fullName: 'Huawei P20 Pro'},
]
}
]
The result of this exmple is:
Complex uses
1- Group number of inputs to one componet
const FIELDS = [
{
key: 'email',
label: 'Email',
component: TextInput
},
{
group: [
{
key: 'firstName',
label: 'FirstName',
component: TextInput
},
{
key: 'lastName',
label: 'LastName',
component: TextInput
},
],
component: FullNameComponent
}
]
In this example FullNameComponent will get props with a object call inputsGroup that include the firstName and lastName component as a function that return the comoponent, you can run the function with props that you eant to pass to the input,
see FullNameComponent as a group component example:
import React from 'react';
export default class FullNameComponent extends React.Component {
render() {
const {inputsGroup} = this.props
return (
<div>
{inputsGroup.firstName({style: {color: 'red'}})}
{inputsGroup.lastName()}
</div>
)
}
}
2 - Get ref of other field and change is value
Sometimes you want to enable value changing in what value to trigger a change in another field, for this case you can get from any input the ref of another input and run is props.change with the value
Example:
In this example AddressAutoComplete is a component with a dropdown of countries, when we select an option we get address and coordinates.
We want to achieve an update in the location input.
We pass in the field config onSelect function that called when user select address from the dropdown, the input will call the onSelect with the option and the input props, one of the props is the getOtherFieldRefByKey function.
we call getOtherFieldRefByKey from the input props and get the refs to location input and is parent.
in this example we use the parent props, the parent is the wrapper of the location input, we call the onChange with the new coordinates.
const FIELDS = [
{
key: 'address',
label: 'Address',
component: AddressAutoComplete,
onSelect: (option, props) => {
const coordinates = option.geometry.coordinates
let locationRefs = props.getOtherFieldRefByKey('location')
const {input, parent} = locationRefs
parent.props.onChange({
__type: 'GeoPoint',
latitude: coordinates[1],
longitude: coordinates[0]
});
},
accessToken: envConfig.MAP_BOX_ACCESS_TOKEN
},
{
key: 'location',
label: 'Location',
component: GeoLocationMapView,
accessToken: envConfig.MAP_BOX_ACCESS_TOKEN
},
]
3 - onChanged example
export const DocFields = [
{
key: 'value',
label: 'Value',
onChanged: (field, props) => {
const displayValue = props.getOtherFieldRefByKey('display').parent.props.value
if(!displayValue || displayValue === '') {
props.getOtherFieldRefByKey('display').parent.props.onChanged(field.value)
}
},
component: TextInput
},
{
key: 'display',
label: 'Display',
validators: { presence: true },
component: TextInput
}
];
Dependencies
- isEmpty/lodash
- validate.js