Themes

Themes are probably the most central piece of React Reform. Use the createTheme function to pass all necessary information about how you want to render your forms.

Minimal Example

To give you a quick impression of how things work let's start with a simple example:

createTheme({

  renderForm: (FormContainer, children) => (
    <FormContainer>
      <div>{children}</div>
      <button>Submit</button>
    </FormContainer>
  ),

  renderField: (Field, {name, validations}) => {
    const errors = validations
      .filter(({isValid}) => isValid === false)
      .map(({errorMessage, name}) => <span key={name}>{errorMessage} </span>)
    return (
      <div>
        <label>{name}</label>
        <Field/>
        {errors.length > 0 && <span>{errors}</span>}
      </div>
    )
  }

})

This results in an unstyled form with it's fields getting basic error messages.

Full Example

Now let's take a look at a more complete Example. While the lines below certainly offer nothing production ready, they certainly should hint at most of what React Reform is capable of and how it's supposed to work.

createTheme({

  renderForm: (FormContainer, children, {directProps: {buttonLabel, style, ...remainingDirectProps}, isValid, status, globalErrors}) => (
    <FormContainer {...remainingDirectProps} style={{background: isValid ? "green" : "red", ...style}}>
      {globalErrors.length ? (
        globalErrors.map((error, i) => <div style={{color: "red"}} key={i}>{error}</div>)
      ) : null}
      <div>{children}</div>
      <button disabled={status === "pending"}>Submit</button>
    </FormContainer>
  ),

  renderField: (Field, {name, validations, directProps, wrapperProps, id, isDirty, isTouched, isFocused, formStatus}) => {
    const errors = validations
      .filter(({isValid}) => isValid === false)
      .map(({errorMessage, name}) => <span key={name}>{errorMessage} </span>)
    const hints = validations.map(({hintMessage, name}) => <span key={name}>{hintMessage} </span>)

    const label = (
      <label htmlFor={id}
        style={{color: errors.length > 0 && isDirty ? "red" : "black"}}
      >{directProps.label || name}</label>
    )

    const field = (
      <Field id={id}
        disabled={formStatus === "pending"}
        style={{border: `1px solid ${isFocused ? "blue" : "black"}`}}
      />
    )

    return (
      <div>
        {wrapperProps.type === "Checkbox" ? (
          <div>{field} {label}</div>
        ) : (
          <div>{label} {field}</div>
        )}
        {hints.length > 0 && <span>{hints}</span>}
        {directProps.explanation && <div className="form-explanaition">{directProps.explanation}</div>}
        {errors.length > 0 && !isTouched && <span>{errors}</span>}
      </div>
    )
  }

  validationLabels: {
    required: {
      errorMessage: (val, {name, arg}) => `'${name}' is really required`
    }
  }

})

createTheme(themeDefinition)

createTheme(themeDefinition) expects a themeDefinition of this shape

const themeDefinition = {
  renderForm: renderFormFn,
  renderField: renderFieldFn,
  validationLabels: validationLabelsObject (optional)
}

This function returns a theme object which can be used either in the ReformContext component or can be passed directly to a form's theme prop.

renderFormFn: (FormContainer, children, data) => {...}

This function allows you to define the body of all the forms using your theme. Typically this includes global error messages (i.e messages that are not specific to a field) and the submit button (if any)

FormContainer

The FormContainer represents the actual <form/> tag. It's up to the theme-author to decide if users may pass props to this Component. In this case you may write something like <FormContainer {...data.directProps}>.

children

As the name suggests, this argument contains the form's children. I.e. the fields of the form.

data.directProps

This field contains all the props that the user passes to the form. This could be used to allow overwriting/extending the style or className of a form.

You may also define your own contracts and allow the user to pass props like buttonLabel or noButton or formWidth to customise the form according to your theme's capabilities.

Note that directProps actually doesn't include all props. theme, children, onSubmit, model, initialModel and onFieldChange get exlcuded.

data.globalErrors

When a form's onSubmit handler returns a promise that gets rejected, you may decide whether to reject with an object describing which fields were the reason, or whether to reject with a string or React element. The latter will be found in the globalErrors list.

data.globalErrors's shape looks like this

[
  'Your error message as a String (if you reject with reject(string)), you may also reject an React element.',
  {fieldName: 'if you reject with `reject({fieldName: string})` and a field with `name="fieldName"` is not present in your form, it'll be here'},
]

data.isValid

If all fields' validations return true, isValid will be true. false in all other cases.

Note that you may define (async) validations that return strings like "pending" in this case isValid will be false as well.

data.validationsPerField

For more fine grained control you may access all validation objects from all fields. This field will have a shape like this:

{
  fieldName1: [
    {name: 'required', isValid: true, errorMessage: '...', hintMessage: '...'},
    ...
  ],
  fieldName2: [
    {name: 'unique', isValid: "pending", errorMessage: '...', hintMessage: '...'}
  ],
}

data.status

This field indicates the current state of the form. status can have these values:

  • 'unsubmitted': the form wasn't submitted yet
  • 'preSubmitFail': the form was submitted but validation errors were found.
  • 'pending': the form's onSubmit handler returned a Promise and was neither resolved nor rejected yet.
  • 'success': the form's onSubmit handler returned a Promise and the Promise was resolved.
  • 'postSubmitFail': the form's onSubmit handler returned a Promise and the Promise was rejected.
  • 'UNCERTAIN STATUS': the form's onSubmit handler returned no promise. This still is a TODO. So please file an issue if you have good generalisable idea for a status here.

data.submitForm

If your theme is attempting some non-standard submit behaviours, you get access to the submit function which will validate the form and invoke the form's onSubmit handler.

Have a look at the Multiple submit buttons recipe to see a use-case.

renderFieldFn: (Field, data) => {...}

This function let's you describe how each field should be rendered and how to display its different states.

Field

The Field contains the wrapped input. You may pass any props to it and may access them via WrapInput's themeProps. The recommended behaviour is to pass all themeProps directly to the input. So please filter your props here in the renderField function.

Typically you want to set className, style or id props. But depending on your requirements you may also set e.g. onBlur handlers.

data.directProps

This field contains all the non-validation props that the user passes to the input (minus the name) prop. Use this to pass style or class information to the input. You may also allow custom props such as label or explanation and deal with those within your theme.

data.name

Contains the required name attribute passed to input. Might act as a good fallback if no better label is present.

data.validations

A list comprised of the validation objects for each validation on this input. It has a shape like this:

[
  {name: 'required', isValid: true, errorMessage: '...', hintMessage: '...'},
  {name: 'unique', isValid: "pending", errorMessage: '...', hintMessage: '...'},
  {name: 'minlength', isValid: false, errorMessage: '...', hintMessage: '...'}
}

data.wrapperProps

When wrapping your inputs you may pass props to the WrapInput component. For example, it's recommended to pass a type prop to help differenatiate between different inputs. Have a look at the Full example above to see how it may help you.

data.directFormProps

Sometimes it might be useful to set some properties on a form that should affect all fields. Think of e.g. <Form onSubmit={...} fieldHeight="big">. directFormProps allows you to access this information.

data.id

Contains a generated and unique id which can be used to bind a label to a input via htmlFor.

data.isDirty

Is true if the input's onChange was called once.

data.isTouched

Is true if the input's onFocus was called once.

data.isFocused

Is true if the input is currently focused.

data.submitForm

As with the renderForm function above, you have access to the form's submit handler to allow for custom submit behaviour. Have a look at the Submit on blur recipe to see how it might help.

data.formStatus

Contains the form's status described in the renderFormFn section above. Use it if you would like to disable the inputs while the form is 'pending'.

validationLabelsObject

Specific themes might want to phrase validation messages differently. To allow for this feature, add a validation object of this shape to overwrite the validation's default messages:

{
  required: {
    errorMessage: (val, {name, arg}) => `'${name}' is really required`,
    hintMessage: (val, {name, arg}) => `'${name}' needs to be set.`,
  }
}

Note that you may omit either hintMessage or errorMessage to fallback to the validation's default message.

React Reform is brought to you by Codecks. Suggest edits for these pages on GitHub