*
to all required fieldsWhen rendering a field, see if there's a validation with name === 'required'
:
import React from 'react'
import {Form, ReformContext, createTheme} from 'react-reform'
import defaultValidations from 'react-reform/opt/validations'
import {Text} from 'react-reform/opt/inputs'
const defaultTheme = createTheme({
renderForm: (FormContainer, children, {directProps}) => (
<FormContainer {...directProps}>
<div>{children}</div>
<button>Submit</button>
</FormContainer>
),
renderField: (Field, {directProps, name, validations, id}) => {
const errors = validations
.filter(({isValid}) => isValid === false)
.map(({errorMessage, name}) => <span key={name}>{errorMessage} </span>)
const isRequired = validations.some(({name}) => name === 'required')
return (
<div>
<label htmlFor={id}>{name}{isRequired && '*'}</label>
<Field id={id} {...directProps}/>
{errors.length > 0 && <span>{errors}</span>}
</div>
)
}
})
export default class ExampleForm extends React.Component {
handleSubmit = (data) => {
console.log('data', data)
}
render() {
return (
<ReformContext themes={{default: defaultTheme}} validations={defaultValidations}>
<div>
<h4>Form</h4>
<Form onSubmit={this.handleSubmit}>
<Text name="required-name" isRequired/>
<Text name="optional-name"/>
</Form>
</div>
</ReformContext>
)
}
}
Look at the directProps
passed to the Form
and filter out a buttonLabel
prop:
import React from 'react'
import {Form, ReformContext, createTheme} from 'react-reform'
import defaultValidations from 'react-reform/opt/validations'
import {Text} from 'react-reform/opt/inputs'
const defaultTheme = createTheme({
renderForm: (FormContainer, children, {directProps: {buttonLabel = 'Submit', ...remainingDirectProps}}) => (
<FormContainer {...remainingDirectProps}>
<div>{children}</div>
<button>{buttonLabel}</button>
</FormContainer>
),
renderField: (Field, {directProps, name, validations, id}) => {
const errors = validations
.filter(({isValid}) => isValid === false)
.map(({errorMessage, name}) => <span key={name}>{errorMessage} </span>)
return (
<div>
<label htmlFor={id}>{name}</label>
<Field id={id} {...directProps}/>
{errors.length > 0 && <span>{errors}</span>}
</div>
)
}
})
export default class ExampleForm extends React.Component {
handleSubmit = (data) => {
console.log('data', data)
}
render() {
return (
<ReformContext themes={{default: defaultTheme}} validations={defaultValidations}>
<div>
<h4>Form</h4>
<Form onSubmit={this.handleSubmit} buttonLabel="Custom button text">
<Text name="field1" isRequired/>
</Form>
</div>
</ReformContext>
)
}
}
If it's a recurring feature within your application it may be worth creating a explicit theme for this.renderForm
offers a submitForm
callback which allows you to pass additional arguments to the onSubmit
handler:
import React from 'react'
import {Form, ReformContext, createTheme} from 'react-reform'
import defaultValidations from 'react-reform/opt/validations'
import {Text} from 'react-reform/opt/inputs'
const defaultTheme = createTheme({
renderForm: (FormContainer, children, {submitForm, directProps: {button1, button2, ...remainingDirectProps}}) => (
<FormContainer {...remainingDirectProps}>
<div>{children}</div>
<button onClick={e => submitForm(e, button1.data)} type={button1.type}>
{button1.label}
</button>
<button onClick={e => submitForm(e, button2.data)} type={button2.type}>
{button2.label}
</button>
</FormContainer>
),
renderField: (Field, {directProps, name, validations, id}) => {
const errors = validations
.filter(({isValid}) => isValid === false)
.map(({errorMessage, name}) => <span key={name}>{errorMessage} </span>)
return (
<div>
<label htmlFor={id}>{name}</label>
<Field id={id} {...directProps}/>
{errors.length > 0 && <span>{errors}</span>}
</div>
)
}
})
export default class ExampleForm extends React.Component {
handleSubmit = (data, event, buttonArgs) => {
console.log('data', data, 'buttonArgs', buttonArgs)
}
render() {
return (
<ReformContext themes={{default: defaultTheme}} validations={defaultValidations}>
<div>
<h4>Form</h4>
<Form onSubmit={this.handleSubmit}
button1={{label: 'Send', type: 'button', data: {close: false}}}
button2={{label: 'Send & Close', type: 'submit', data: {close: true}}}
>
<Text name="comment" isRequired/>
</Form>
</div>
</ReformContext>
)
}
}
Here we're creating a theme that allows to have no button by default. So instead we're passing our own buttons to the form. The second button is our submit
button and will execute the default onSubmit
handler. The first button gets an onClick
handler which calls handleSubmit
on the form's ref
. This function expects an event as an argument. Instead of this, we just pass a string signaling to the submit handler that this is a special case.
Calling handleSubmit
will perform all validation checks, so your onSubmit
won't be called if there's still invalid data.
import React from 'react'
import {Form, ReformContext, createTheme} from 'react-reform'
import defaultValidations from 'react-reform/opt/validations'
import {Text} from 'react-reform/opt/inputs'
const defaultTheme = createTheme({
renderForm: (FormContainer, children, {directProps: {noButton, ...remainingDirectProps}}) => (
<FormContainer {...remainingDirectProps}>
<div>{children}</div>
{!noButton && <button>Submit</button>}
</FormContainer>
),
renderField: (Field, {directProps, name, validations, id}) => {
const errors = validations
.filter(({isValid}) => isValid === false)
.map(({errorMessage, name}) => <span key={name}>{errorMessage} </span>)
return (
<div>
<label htmlFor={id}>{name}</label>
<Field id={id} {...directProps}/>
{errors.length > 0 && <span>{errors}</span>}
</div>
)
}
})
export default class ExampleForm extends React.Component {
handleSendClick = () => {
this.formNode.handleSubmit('noClose')
}
handleSubmit = (data, event) => {
console.log('data', data, 'close', event !== 'noClose')
}
render() {
return (
<ReformContext themes={{default: defaultTheme}} validations={defaultValidations}>
<div>
<h4>Form</h4>
<Form onSubmit={this.handleSubmit} noButton ref={node => this.formNode = node}>
<Text name="comment" isRequired/>
<div>
<button type="button" onClick={this.handleSendClick}>Send</button>
<button type="submit">Send & Close</button>
</div>
</Form>
</div>
</ReformContext>
)
}
}
submitForm
is also available in the renderField
function. This can be called when the input is blurred.
There's a special case here. When validation fails, React Reform focusses the first invalid field by default. In our submit-on-blur case however, this would result in focussing the field immediately blurring if there's an validation error. This behaviour can be skipped via setting dontFocusAfterFail
on the field.
import React from 'react'
import {Form, ReformContext, createTheme} from 'react-reform'
import defaultValidations from 'react-reform/opt/validations'
import {Text} from 'react-reform/opt/inputs'
const defaultTheme = createTheme({
renderForm: (FormContainer, children, {directProps}) => (
<FormContainer {...directProps}>{children}</FormContainer>
),
renderField: (Field, {directProps, submitForm, name, validations, id}) => {
const errors = validations
.filter(({isValid}) => isValid === false)
.map(({errorMessage, name}) => <span key={name}>{errorMessage} </span>)
return (
<div>
<label htmlFor={id}>{name}</label>
<Field id={id} onBlur={submitForm} dontFocusAfterFail {...directProps}/>
{errors.length > 0 && <span>{errors}</span>}
</div>
)
}
})
export default class ExampleForm extends React.Component {
handleSubmit = (data) => {
console.log('data', data)
}
render() {
return (
<ReformContext themes={{default: defaultTheme}} validations={defaultValidations}>
<div>
<h4>Form</h4>
<Form onSubmit={this.handleSubmit}>
<Text name="field1" hasMinlength={5}/>
</Form>
</div>
</ReformContext>
)
}
}
When using a controlled form you can render your form using different fields or validation rules:
import React from 'react'
import {Form, ReformContext, createTheme} from 'react-reform'
import defaultValidations from 'react-reform/opt/validations'
import {Text, Textarea, Select} from 'react-reform/opt/inputs'
const defaultTheme = createTheme({
renderForm: (FormContainer, children, {directProps}) => (
<FormContainer {...directProps}>
<div>{children}</div>
<button>Submit</button>
</FormContainer>
),
renderField: (Field, {directProps, name, validations, id}) => {
const errors = validations
.filter(({isValid}) => isValid === false)
.map(({errorMessage, name}) => <span key={name}>{errorMessage} </span>)
return (
<div style={{marginBottom: 15}}>
<label htmlFor={id}>{name}</label>
<Field id={id} style={{display: 'block'}} {...directProps}/>
{errors.length > 0 && <span>{errors}</span>}
</div>
)
}
})
export default class ExampleForm extends React.Component {
state = {model: {
formVariant: 'one',
changingMinlength: 'inital',
withinInFormVariant1: null,
withinInFormVariant2: null
}}
handleSubmit = (data) => {
console.log('data', data)
}
handleFieldChange = (fieldName, value) => {
this.setState({model: {...this.state.model, [fieldName]: value}})
}
render() {
const {model} = this.state
return (
<ReformContext themes={{default: defaultTheme}} validations={defaultValidations}>
<div>
<h4>Form</h4>
<Form onSubmit={this.handleSubmit} model={model}
onFieldChange={this.handleFieldChange}
>
<Select name="formVariant">
<option value="one">Variant one</option>
<option value="two">Variant two</option>
</Select>
<Text name="changingMinlength" hasMinlength={model.formVariant === 'one' ? 10 : 20}/>
{model.formVariant === 'one' ? (
<Textarea rows={5} name="withinInFormVariant1" isRequired/>
) : (
<Text name="withinInFormVariant2" isEmail/>
)}
</Form>
</div>
</ReformContext>
)
}
}