Usage¶
Dependencies¶
As the peer dependencies indicate, the React JSON Schema Form Builder depends on the bootstrap package. Ensure that it is installed in your app, and include the stylesheet by importing it in the main module of your app:
import 'bootstrap/dist/css/bootstrap.min.css';
FormBuilder Component¶
The following example is a React component that simply maintains a Form Builder and stores the corresponding JSON schema as state variables, instead of rendering them in any way.
import React, { Component } from 'react';
import {FormBuilder} from 'react-json-schema-form-builder';
class Example extends Component {
constructor(props) {
super(props);
this.state = {
schema: '{}',
uischema: '{}'
};
}
render() {
return (
<FormBuilder
schema={this.state.schema}
uischema={this.state.uischema}
onChange={(newSchema, newUiSchema) => {
this.setState({
schema: newSchema,
uischema: newUiSchema
})
}}
/>
);
}
}
With React JSON Schema Form¶
Most likely, you will want to visually build the form with some preview of the rendered form available.
This will require you to use some implementation of the Form
component from RJSF. React component to render the JSON schema as a form. This example uses @rjsf/core:
npm i --save @rjsf/core
The following example uses this form preview adjacent to the Form Builder:
import React, { Component } from 'react';
import {FormBuilder} from 'react-json-schema-form-builder';
import Form from '@rjsf/core';
class Example extends Component {
constructor(props) {
super(props);
this.state = {
schema: '{}',
uischema: '{}',
formData: {}
};
}
render() {
return (
<div>
<FormBuilder
schema={this.state.schema}
uischema={this.state.uischema}
onChange={(newSchema, newUiSchema) => {
this.setState({
schema: newSchema,
uischema: newUiSchema
})
}}
/>
<Form
schema={JSON.parse(this.state.schema)}
uiSchema={JSON.parse(this.state.uischema)}
onChange={(newFormData) => this.setState(formData: newFormData.formData)}
formData={this.state.formData}
submitButtonMessage={"Submit"}
/>
</div>
);
}
}
With Definitions¶
JSON Schema forms also make use of definitions, allowing a form builder to define a component once and have several instances of that same component elsewhere within the schema. The PredefinedGallery
component reads a whole JSON schema and allows a user to edit the definitions
section. This is meant to be used in tandem with the FormBuilder
, as follows:
import React, { Component } from 'react';
import {FormBuilder, PredefinedGallery} from "@ginkgo-bioworks/react-json-schema-form-builder";
class Example extends Component {
constructor(props) {
super(props);
this.state = {
schema: '{}',
uischema: '{}'
};
}
render() {
return (
<div>
<FormBuilder
schema={this.state.schema}
uischema={this.state.uischema}
onChange={(newSchema, newUiSchema) => {
this.setState({
schema: newSchema,
uischema: newUiSchema
})
}}
/>
<PredefinedGallery
schema={this.state.schema}
uischema={this.state.uischema}
onChange={(newSchema, newUiSchema) => {
this.setState({
schema: newSchema,
uischema: newUiSchema
})
}}
/>
</div>
);
}
}
Function Hook Example¶
The following is an example that implements the FormBuilder
in a React function hook:
import React, { useState } from 'react';
import {FormBuilder, PredefinedGallery} from "@ginkgo-bioworks/react-json-schema-form-builder";
export default function Example() {
const [schema, setSchema] = useState('{}');
const [uischema, setUiSchema] = useState('{}');
return (
<div>
<FormBuilder
schema={schema}
uischema={uischema}
onChange={(newSchema, newUiSchema) => {
setSchema(newSchema);
setUiSchema(newUiSchema)
}}
/>
<PredefinedGallery
schema={schema}
uischema={uischema}
onChange={(newSchema, newUiSchema) => {
setSchema(newSchema);
setUiSchema(newUiSchema)
}}
/>
</div>
);
}
Advanced¶
Custom Form Inputs¶
In addition to the default types of form inputs (Time, Checkbox, Radio, Dropdown, Short Answer, Long Answer, Password, Integer, Number, Array), custom form inputs can also be specified. These form inputs are defined in a JS object that is passed into the FormBuilder
component (and the PredefinedGallery
component if it's being used) as part of a mods
property, which has a comprehensive type definition in src/formBuilder/types.js as Mods
.
Example Custom Form Input¶
const customFormInputs = {
shortAnswer: {
displayName: "Email",
matchIf: [
{
types: ["string"],
widget: "email"
},
],
defaultDataSchema: {},
defaultUiSchema: {
"ui:widget": "password"
},
type: "string",
cardBody: (parameters, onChange) => <div>
<h5>Default email</h5>
<input
value={parameters.default}
placeholder="Default"
type="text"
onChange={(ev) =>
onChange({ ...parameters, default: ev.target.value })
}
/>
</div>,
modalBody: (parameters, onChange) => <div>
Extra editing options in modal appear hear
</div>,
},
};
This can then be passed into an app using the FormBuilder
as follows:
import React, { Component } from 'react';
import { FormBuilder } from 'react-json-schema-form-builder';
class Example extends Component {
constructor(props) {
super(props);
this.state = {
schema: '',
uischema: ''
};
}
render() {
return (
<FormBuilder
schema={this.state.schema}
uischema={this.state.uischema}
onChange={(newSchema, newUiSchema) => {
this.setState({
schema: newSchema,
uischema: newUiSchema
})
}}
mods={
{
customFormInputs
}
}
/>
);
}
}
The customFormInputs
define the logic that translates abstract "Input Types" into raw data schema and UI schema. For more information about these Custon Form Inputs, see the page here.
The tooltipDescriptions
allows an implementation of the FormBuilder
that changes the tooltip descriptions that appear on hover over certain areas of the tool. The add
popup appears when hovering over the plus buttons, the cardObjectName
is the name of the back end name that appears in every card object input, the cardDisplayName
allows rewriting the description of the display name tooltip, the cardDescription
option allows overwriting the tooltip for the description, and the cardInputType
allows setting a custom tooltip for the Input Type dropdown.
Deactivated Form Inputs¶
It is also possible to deactivate (hide) certain Input Types by setting the deactivatedFormInputs
property on mods. For example, to hide the time
and checkbox
form inputs that are usually included by default, you may set the mods to:
mods = {
deactivatedFormInputs: ['time', 'checkbox'],
}
This will hide these Input Types on the FormBuilder
component.
Default New Form Element (newElementDefaultDataOptions and newElementDefaultUiSchema)¶
By default, when adding a new form element, the schema for the new form element is set to:
{
title: `New Input ${i}`,
type: 'string',
default: '',
}
and the UI schema is not set at all. This means that by default, a new form element has the "Short answer" input type. If you wish to override this (for example, if the "Short answer" input is deactivated), you can do so by using the newElementDefaultDataOptions
and newElementDefaultUiSchema
mods. For example, setting the mods to the following:
mods = {
newElementDefaultDataOptions: {
'$ref': '#/definitions/firstNames',
title: 'Field',
},
};
will default new form elements to a "Reference" type to some definition "firstNames" defined in the schema. Setting the mods to the following:
mods = {
newElementDefaultUiSchema: {
'ui:widget': 'customWidget',
}
};
will set the UI schema for a new element to use the customWidget
widget.
Customizing "Add" buttons¶
This form builder includes a button to add new form elements or sections (the button with a + symbol). In some cases you may want to modify this feature or provide custom button components. There are a few options to customize this component.
1. Custom Labels¶
One simple way to provide customization is to change the labels for "Add form element" and "Add form section". You can update these labels with the following mods:
const mods = {
//...
labels: {
//...
addElementLabel: 'my element label',
addSectionLabel: 'my section label',
}
}
2. Invoking the "add" functions¶
You can invoke the following two functions anywhere in your app in order to add form elements and/or sections within the Form Builder component:
import { addCardObj, addSectionObj } from '@ginkgo-bioworks/react-json-schema-form-builder';
Both of these functions require the following properties:
type properties = {
schema: object, // FormBuilder schema
uischema: object, // FormBuilder uiSchema
mods: object, // FormBuilder mods
onChange: (newSchema: object, newUiSchema: object) => {
// update schemas when button clicked...
},
definitionData: string, // see: schema.definitions
definitionUi: string, // see: uischema.definitions
categoryHash: [string], // see: FormBuilder.onMount()
};
The categoryHash
helps FormBuilder match input types and is generated by FormBuilder upon rendering. You can get the generated hash through the onMount
callback.
Example:
<FormBuilder
//...
onMount={({ categoryHash }) => {
// Here you can save categoryHash to props or state
setCategoryHash(categoryHash);
}}
/>
3. Overriding the "Add" component¶
By providing a custom component through mods
, you can completely override the Add
component.
To do this, provide a callback function to the components.add
mod. This callback should expect one argument, which provides properties that are required to add elements and sections to the form builder.
import { addCardObj } from '@ginkgo-bioworks/react-json-schema-form-builder';
mods = {
components: {
add: (properties) => <MyComponent onClick={() => { addCardObj(properties) }} />
}
}
Putting it all together, the following snippet is an example showing two fully functioning buttons:
import React, { useState } from 'react';
import { addCardObj, addSectionObj } from '@ginkgo-bioworks/react-json-schema-form-builder';
const mods = {
//...
};
function MyFormBuilder() {
const [schema, setSchema] = useState({});
const [uiSchema, setUiSchema] = useState({});
const [categoryHash, setCategoryHash] = useState('');
mods.components: {
add: (addProps) => {
return (
<div>
<Button onClick={() => { addCardObj(addProps) }}>Add Form Element</Button>
<Button onClick={() => { addSectionObj(addProps) }}>Add Form Section</Button>
</div>
);
},
};
return (
<FormBuilder
schema={JSON.stringify(schema)}
uischema={JSON.stringify(uiSchema)}
mods={mods}
onChange={(newSchema, newUiSchema) => {
setSchema(JSON.parse(newSchema));
setUiSchema(JSON.parse(newUiSchema));
}}
/>
);
}
Styling¶
To avoid collisions with existing CSS styles, this app uses react-jss in order to generate class names avoiding overlap with others in the global scope. Using CSS to style FormBuilder and PredefinedGallery components will not work and is not supported. The ability to "skin" the FormBuilder and PredefinedGallery components may be a feature in the future.