Recipes

See some typical patterns in action

Add a * to all required fields

When 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>
    )
  }
}

See it in action

Form

is required
Check your console to see the submitted value

Custom button text

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>
    )
  }
}

See it in action

Form

is required
Check your console to see the submitted value

Multiple submit buttons

Approach 1: Create a theme

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>
    )
  }
}

See it in action

Form

is required
Check your console to see the submitted value

Approach 2: Create a custom handler in your form

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>
    )
  }
}

See it in action

Form

is required
Check your console to see the submitted value

Submit on blur

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>
    )
  }
}

See it in action

Form

minimal length: 0/5
Check your console to see the submitted value

Dynamic fields

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>
    )
  }
}

See it in action

Form

minimal length: 6/10
is required
Check your console to see the submitted value
React Reform is brought to you by Codecks. Suggest edits for these pages on GitHub