Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@data-driven-forms/react-form-renderer

Package Overview
Dependencies
Maintainers
1
Versions
388
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@data-driven-forms/react-form-renderer

React form renderer for data-driven-forms.

  • 1.14.2
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
11K
increased by7.45%
Maintainers
1
Weekly downloads
 
Created
Source

React Form Renderer

Rendering forms from data structures

Online demo: http://data-driven-forms.surge.sh/

Table of contents

Instalation

npm install --save @data-driven-forms/react-form-renderer

or

yarn add @data-driven-forms/react-form-renderer

Getting started

React form renderer is a component designed for ManageIQ and Insighs projects that takes json form definitions and renders them into react components. It uses React final form for the form state management. It is highly recommended to check their documentations first to fully understand how the data-driven-forms libraries work.

Code examples:

import FormRender from '@data-driven-forms/react-form-renderer';

const DataDrivenForm = () => (
  <FormRender
    formFieldsMapper={formFieldsMapper}
    layoutMapper={layoutMapper}
    schema={schema}
    onSubmit={...}
    onCancel={...}
    onReset={...}
    canReset
  />
);

There are several required props that must be passed to the component. Check the examples below to learn how it works.

Form schemas

There are currently 3 schema definitions you can use to define your forms. With the intention to provide additional customization in the future. Currently supported schemas are:

Default schema

This is the default schema that is used directly for rendering the form. All other schema types are parsed to this one. This gives the option to write your custom parser that transforms any of your existing definitions into the default one, and use this renderer.

The default schema is also very extensible. There is only a few requirements for the format. Most of the attributes are meta information and their shape is based upon your form components.

import { componentTypes, validatorTypes } from '@data-driven-forms/react-form-renderer';

const schema = {
  title: 'My form title',
  description: 'My form description',
  fields: [{
    component: componentTypes.TEXT_FIELD,
    name: 'first-name',
    label: 'First name'
    validate: [{
      type: validatorTypes.REQUIRED,
      message: 'First name is required'
    }, {
      type: validatorTypes.MIN_LENGTH,
      treshold: 3,
      message: 'First name must be at least 3 characters long'
    }]
  }, {
    component: componentTypes.TEXT_FIELD,
    type: 'password',
    name: 'password',
    label: 'password',
  }]
}

Example above shows definition of a very simple form with two form fields and a validation. We will now take a closer look at its attributes.

default-schema-attributes
namedata type
title?string
description?string
fieldsArray of Objects

Detailed descriptions of each attribute is below.

title?: string

Attribute defining form title.

description?: string

Attribute defining form description.

fields: Array.<Object>

Array that contains field definitions.

Fields

This is the main data structure that holds definitions of all of the form fields. It is designed to match React rendering process. It must follow this rule:

const fields = [{...}, [{...}, {...}], {...}, {...}, [[[{...}]]]]

In human language, items of field array must be either objects, where each object represents one formField (React component), or array of objects, which are form fields as well. This rule allows the component to render all the fields in one cycle with minimal code branching.

The structure of a single object is following:

field attributes

There are listed all field (items of the fields array) attributes that are defined by the default schema. Any other attributes given to field object are automatically passed to the specified component.

Detailed descriptions of each attribute is below.

nametype
componentstring
namestring
validate?Array of Objects
condition?Object
dataTypestring
?bool
SubForm only
fieldsArray (only for SUB_FORM)
title?string
description?string
import { componentTypes, validatorTypes } from '@data-driven-forms/react-form-renderer';

const field = {
  component: componentTypes.TEXT_FIELD,
  name: 'first-name',
  label: 'First name',
  ...
}

Note that the field structure may vary based on your component implementation. There are few required attributes and most of them do not have to match the given types. Most of them are based on used form components.

component: string

Unique identifier of the component. Final component will be picked based on this key. There are several pre-defined constants identifying the most common components for ManageIQ and Insights apps.

import { componentTypes } from '@data-driven-forms/react-form-renderer';

componentTypes = {
  TEXT_FIELD: 'text-field',
  TEXTAREA_FIELD: 'textarea-field', // deprecated, please use TEXTAREA
  FIELD_ARRAY: 'field-array', 
  SELECT_COMPONENT: 'select-field', // deprecated, please use SELECT
  FIXED_LIST: 'fixed-list',
  CHECKBOX: 'checkbox',
  SUB_FORM: 'sub-form',
  RADIO: 'radio',
  TABS: 'tabs',
  TAB_ITEM: 'tab-item',
  DATE_PICKER: 'date-picker',
  TIME_PICKER: 'time-picker',
  TAG_CONTROL: 'tag-control',
  SWITCH: 'switch',
  TEXTAREA: 'textarea-field',
  SELECT: 'select-field',
}

We are not limited by these component types. You can add your own type or use only few of them or combination of both. More detailed explanation of how this impacts the rendered form can be found here.

name: string

This is traditional html5 name attribute for input elements.

label

Label for form field. The type is based on your component definition.

validate: Array?<Object>

Array of validation definitions. These are limited by the form renderer (Might be configurable in future).

If you want to use out of the box validation, you must use this format:

const validate = [{
  type: string,
  message: string?,
  ...
}]

Each validator type has additional configuration options in addition to custom error message:

import { validatorTypes } from '@data-driven-forms/react-form-renderer';

validatorTypes = {
  REQUIRED: 'required-validator',
  /**
  * min length if the input value
  */
  MIN_LENGTH: 'min-length-validator',
  /**
  * minimum count of fileds in some dynamic list of fields
  */
  MIN_ITEMS_VALIDATOR: 'min-items-validator',
  /**
  * Minimum value of number input
  */
  MIN_NUMBER_VALUE: 'min-number-value',
  /**
   * Maximum value of number inpuy
  */
  MAX_NUMBER_VALUE: 'max-number-value',
  /**
  * Regexp pattern validator
  */
  PATTERN_VALIDATOR: 'pattern-validator',
}

const validate = [{
  type: validatorTypes.REQUIRED,
  message: 'This is custom error message'
}, {
  type: validatorTypes.MIN_LENGTH,
  treshold: integer
}, {
  type: validatorTypes.MIN_ITEMS_VALIDATOR,
  treshold: integer
}, {
  type: validatorTypes.MIN_NUMBER_VALUE
  value: integer
}, {
  type: validatorTypes.MAX_NUMBER_VALUE
  value: integer
}, {
  type: PATTERN_VALIDATOR,
  pattern: string // regex pattern
  showPatter: bool? // if message is not define turns on/of pattern in error message
}]

Validation functions are triggered only when field has a value with exception of required validator.

dataType: string?

Adds field validation based on the value data type.

import { componentTypes } from '@data-driven-forms/react-form-renderer';

const field = {
  component: componentTypes.TEXT_FIELD,
  name: 'number',
  type: 'number',
  label: 'Integer number',
  dataType: 'integer',
}

There are currently four defined data types:

['integer', 'number', 'bool', 'string']

#### `condition: Object?`
Condition is used to define condition fields. For instance, field **A** should render only when field **B** has value **Foo**.

```javascript
import { componentTypes } from '@data-driven-forms/react-form-renderer';

const fields = [{
  component: componentTypes.TEXT_FIELD,
  name: 'Foo'
}, {
  component: componentTypes.TEXT_FIELD,
  name: 'Bar'
  condition: {
    when: 'Foo',
    is: 'Show Bar field',
  }

}]

Sometimes you might want to show field when it's matching multiple values:

import { componentTypes } from '@data-driven-forms/react-form-renderer';

const fields = [{
  component: componentTypes.TEXT_FIELD,
  name: 'Foo'
}, {
  component: componentTypes.TEXT_FIELD,
  name: 'Bar'
  condition: {
    when: 'Foo',
    is: ['Show Bar field', true, 123, 'Or now'],
  }
}]

In example above, field Bar will appear when fields Foo value is Show bar field, true, 123 or Or now.

Other attributes

Any other attributes will be passed to the component matching the component identifier.

For examples definition of select component might look something like this:

import { componentTypes } from '@data-driven-forms/react-form-renderer';

const field = {
  component: componentTypes.SELECT_COMPONENT,
  name: 'color'
  label: 'Choose your favorite color'
  options: [{
    label: 'Red',
    value: 'red'
  }, {
    label: 'Blue',
    value: 'blue',
  }, {
    label: 'Green',
    value: 'sneaky-red',
  }]
}

Remember that the components define the interface. If your label is an image, pass the image source with isImage flag maybe and handle rendering in the component.

Field array and Fixed list

TO DO add documentaion here

Component Mapping

As it was already mentioned, you can define your own components for rendering. In fact, you have to define them because the Form Renderer does not know anything about them. This way, the renderer is universal and can be used with any component library or your custom components. It is also very easy to swap the look of the form without any changes to the format!

We also understand that writing form components from the scratch might not be very user friendly. ManageIQ and Insights are using patternfly style patterns, so you can get inspired with Patternfly 3 and Patternfly 4 mappers.

Form renderer requires two different mappers. Layout mapper and Form FieldsMapper.

Layout mapper

Component inside this mapper influence the layout of the form. Now compared to the Form Fields mapper, we have to be very strict because we cannot define our own elements. Layout mapper must contain these types components:

import { layoutComponents } from '@data-driven-forms/react-form-renderer';

const layoutComponents = {
  [layoutComponents.FORM_WRAPPER]: 'FormWrapper',
  [layoutComponents.BUTTON]: 'Button',
  [layoutComponents.COL]: 'Col',
  [layoutComponents.FORM_GROUP]: 'FormGroup',
  [layoutComponents.BUTTON_GROUP]: 'ButtonGroup',
  [layoutComponents.ICON]: 'Icon',
  [layoutComponents.ARRAY_FIELD_WRAPPER]: 'ArrayFieldWrapper',
  [layoutComponents.HELP_BLOCK]: 'HelpBlock'
}

LayoutMapper is just good old javascript object with keys from layoutComponents, and the values are just React components:

FormWrapper

Form wrapper is your form wrapper component. Typically it will be your react version of <form> tag:

import FormRenderer, { layoutComponents } from '@data-driven-forms/react-form-renderer';

const FormWrapper = ({ children }) => <form>{children}</form>

const layoutMapper = {
  [layoutComponents.FORM_WRAPPER]: FormWrapper,
}

const MyForm = () => (
  <FormRenderer
    layoutMapper={layoutMapper}
  />
)
Button

Button component will be used for your submit, reset and cancel buttons.

import layoutComponents } from '@data-driven-forms/react-form-renderer';

const Button = ({ label, bsStyle, ...props }) => (
  <button
    {...props}
    style={{ backgroud: bsStyle === primary ? 'blue' : 'initial' }}
  >
    {label}
  </button>
)
const layoutMapper = {
  [layoutComponents.BUTTON]: FormWrapper,
}
Col

Col represents wrapper arround one Form Field (hence the name Col). It does not have to mirror bootstrap Col, which is just the name we have decided to go with. If you for instance don't need any Col (or other wrapping) component, and you are handling this inside the actual Field component, you can use <React.Fragment> component. This way you will not create any element in your DOM. On the other hand, it might be usefull to implement it as a container for your components. Because we can't possibly create layout that suit 100% of our use cases, we can use this wrapper to pass additional styles to field components.

import './form/styles.scss';

const Col = ({ children }) => (
  <div className="form-row">{children}</div>
)
FormGroup

Very similar to Col component.

import './form/styles.scss';

const FormGroup = ({ children }) => (
  <div className="form-group">{children}</div>
)
ButtonGroup

Wrapper for your form buttons

import './form/styles.scss';

const ButtonGroup = ({ children }) => (
  <div className="button-group">{children}</div>
)
Icon, Array Field Wrapper, Help Block

TO DO when array field docs are done

Putting it all together
import FormRenderer, { layoutComponents } from '@data-driven-forms/react-form-renderer';

const layoutMapper = {
  [layoutComponents.FORM_WRAPPER]: FormWrapper,
  [layoutComponents.BUTTON]: Button,
  [layoutComponents.COL]: Col,
  [layoutComponents.FORM_GROUP]: FormGroup,
  [layoutComponents.BUTTON_GROUP]: ButtonGroup,
  [layoutComponents.ICON]: Icon,
  [layoutComponents.ARRAY_FIELD_WRAPPER]: ArrayFieldWrapper,
  [layoutComponents.HELP_BLOCK]: HelpBlock
}

const MyForm = () => (
  <FormRenderer
    ...
    layoutMapper={layoutMapper}
    ...
  />
)

Form Fields mapper

Unlike the layout components, the form fields are completely customizable, and the implementation is restricted to only one rule. In order to correctly change the form state, you have to use provided input and meta props to your input fields. These objects provide functions like onChange, onBlur, error messages, valid state and more. Again you should probably read more about that in the React Final Form docs.

In an example below you can see an implementation of a simple input component using both predefined component type and a custom one.

import { componentTypes, validatorTypes } from '@data-driven-forms/react-form-renderer';

// if no id is provided field will use name and assign it to ID
const DefaultInput = ({ input, meta, label, name, ...rest }) => {
  const id = rest.id || name;
  return (
    <div className={`form-group ${meta.error ? 'error' : ''}`}>
      <label htmlFor={id}>{label}</label>
      <input id={id} name={name} {...rest} {...input} />
      {meta.error && <span className="error-text">{meta.error}</span>}
    </div>
  )
}

const CustomInput = ({ FieldProvider, ...rest }) => (
  <FieldProvider
    {...rest}
    component={DefaultInput}
  />
)

const formFieldsMapper = {
  [componentTypes.TEXT_FIELD]: DefaultInput,
  'custom-input': CustomInput,
}

const schema = {
  fields:[{
    component: componentTypes.TEXT_FIELD.
    name: 'first-name',
    label: 'First Name',
    type: 'text',
    validate: [{
      type: validatorTypes.REQUIRED,
    }]
  }, {
    component: 'custom-input'.
    name: 'last-name',
    label: 'Last Name',
    type: 'text',
    validate: [{
      type: validatorTypes.REQUIRED,
    }]
  }]
}

What about nesting?

There might be a need to create something like a SubForm in your Forms. We made it possible, and there are two ways to do it.

  1. Use pre defined componentTypes.SUB_FORM component type:
import { componentTypes } from '@data-driven-forms/react-form-renderer';

const schema = {
  fields: [{
    component: componentTypes.TEXT_FIELD,
    name: 'first-name',
    ...
  }, {
    component: componentTypes.SUB_FORM,
    title: 'Address',
    description: 'Please provide us with your shipping address.',
    name: 'address',
    fields: [{
      component: componentTypes.TEXT_FIELD,
      name: 'city',
      label: 'City',
      validate: [...],
      ...
    }]
  }]
}

This renders and bundles fields and their values inside the SUB_FORM component. When the form is submitted, the values object will look something like this:

{
  "first-name": "Bob",
  "address": {
    "city": "Prague"
    ...
  }
}
  1. Custom component Again, the SUB_FORM component type might not be the correct solution for your use case. You can always create your own. Let's use the same schema as in example above, but only with one change:
component: componentTypes.SUB_FORM, -> component: 'custom-sub-form'

If you use custom type, in addition to all the attributes in the field specification, you will also receive formOptions props. Form options contains some global form properties, and most importantly, there is a renderForm function. It does exactly what it says. It is the main loop that renders the form. It accepts two arguments, fields and formOptions:

const CustomSubForm = ({ formOptions, fields, ...rest }) => (
  <SubFormWrapper>
    {formOptions.renderForm(fields, {
      ...formOptions,
      someExtendingAttribute: 'foo'
    })}
  </SubFormWrapper>
)

FormOptions are passed to every field (default and custom). If you extend it, the child of the sub form will receive your modified formOptions. You can even change te rendering function if you wish.

Keywords

FAQs

Package last updated on 01 Aug 2019

Did you know?

Socket

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.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc