Skip to main content

SchemaBasedUIForms

react-jsonschema-form is meant to automatically generate a React form based on a JSON Schema. If you want to generate a form for any data, sight unseen, simply given a JSON schema, react-jsonschema-form may be for you. If you have a priori knowledge of your data and want a toolkit for generating forms for it, you might look elsewhere.

react-jsonschema-form also comes with tools such as uiSchema and other form props to customize the look and feel of the form beyond the default themes.

Usage

import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';

const schema: RJSFSchema = {
title: 'Todo',
type: 'object',
required: ['title'],
properties: {
title: { type: 'string', title: 'Title', default: 'A new task' },
done: { type: 'boolean', title: 'Done?', default: false },
},
};

const log = (type) => console.log.bind(console, type);

render(
<Form
schema={schema}
validator={validator}
onChange={log('changed')}
onSubmit={log('submitted')}
onError={log('errors')}
/>,
document.getElementById('app'),
);

JSON Schema

JSON Schema is a format for describing the structure and constraints of JSON data, widely used in React JSONSchema Form (rjfs) to define data models and validation rules for forms.

Basics

  • type Property: Specifies the expected data type, such as "string", "number", "integer", "boolean", "object", or "array".

  • properties Property: Defines fields within an object. Each field is described as a key-value pair, where the key is the field name, and the value is another JSON Schema describing the field.

  • required Property: An array listing fields that must be present in the object.

Common Constraints

  • Additional Properties: Various properties like "minimum", "maximum", "enum", etc., can be used to set constraints based on the data type.

Example Usage

import { render } from 'react-dom';
import { Form } from '@rjsf/core';

const jsonSchema = {
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
age: { type: 'integer', minimum: 0 }
},
required: ['firstName', 'lastName']
};

render(
<Form schema={jsonSchema} />,
document.getElementById('app')
);

UI Schema in JSON Form

The UI Schema in React JSON Form (rjfs) serves as a configuration object, dictating the visual representation and behavior of form elements. It allows for customization of the appearance and functionality of different fields within a form.

const uiSchema = {
// ... fields definition
};

Structure of the UI Schema

The UI Schema provided organizes the layout and styling for distinct groups of fields related to pricing information within a form.

price: {
// ... price-related fields
},
additionalPrice: {
// ... additional price-related fields
}

Fields in UI Schema

Each field in the UI Schema corresponds to a specific data field in the form. Properties associated with these fields control their appearance and behavior.

priceType: { 'ui:colSpan': 5 },
pricePerGuestEnabled: { 'ui:widget': 'SwitchWidget' },

Widgets in UI Schema

Widgets specify the type of UI element used to display a particular field, influencing how the input is rendered.

'ui:widget': 'SwitchWidget',

Templates in UI Schema

Templates enable the customization of the layout and structure for groups of related fields, aiding in organization and styling.

No specific example is provided in the given UI Schema.

ui:colSpan for Input Field Size

The ui:colSpan property is utilized to define the size of the input field, indicating the number of columns it should span within the form layout.

'ui:colSpan': 7,

ui:field for Custom Input Components

The ui:field property designates a custom input component (widget) for a particular field, allowing for the integration of specialized UI elements.

'ui:colSpan': 6, 'ui:field': 'InputWithDropdown',

Widget and Field Customization

  • Widget: Represents an HTML tag for the user to input data (e.g., input, select, etc.).

  • Field: Typically wraps one or more widgets and often manages internal field state, akin to a form row, including labels.

Customizing Default Fields and Widgets

You have the flexibility to override any default field and widget, including internal widgets like CheckboxWidget used by ObjectField for boolean values. This can be achieved by providing customized fields and widgets in the fields and widgets props.

import { RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';

const schema: RJSFSchema = {
type: 'boolean',
default: true,
};

const uiSchema: UiSchema = {
'ui:widget': 'checkbox',
};

const CustomCheckbox = function (props: WidgetProps) {
return (
<button
id="custom"
className={props.value ? 'checked' : 'unchecked'}
onClick={() => props.onChange(!props.value)}
>
{String(props.value)}
</button>
);
};

const widgets: RegistryWidgetsType = {
CheckboxWidget: CustomCheckbox,
};

render(
<Form schema={schema} uiSchema={uiSchema} validator={validator} widgets={widgets} />,
document.getElementById('app'),
);

Custom Component Registration

Another approach to registering custom components in React JSON Form (rjfs) involves registering them collectively by passing the widgets prop to the Form component. The registered components can then be referenced from the uiSchema.

import { RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';

// Define a custom widget, MyCustomWidget
const MyCustomWidget = (props: WidgetProps) => {
return (
<input
type="text"
className="custom"
value={props.value}
required={props.required}
onChange={(event) => props.onChange(event.target.value)}
/>
);
};

// Register the custom widget
const widgets: RegistryWidgetsType = {
myCustomWidget: MyCustomWidget,
};

// Define a schema for a string field
const schema: RJSFSchema = {
type: 'string',
};

// Reference the custom widget in the uiSchema
const uiSchema: UiSchema = {
'ui:widget': 'myCustomWidget',
};

// Render the form with the registered custom widget
render(
<Form schema={schema} uiSchema={uiSchema} validator={validator} widgets={widgets} />,
document.getElementById('app'),
);

Template Customization

BaseInputTemplate

The BaseInputTemplate serves as the template for rendering the basic <input> component within a theme in React JSON Form (rjfs). It acts as the foundation for rendering various <input>-based widgets that may differ by type and callbacks only.

For example, the TextWidget implementation in the core is essentially a wrapper around BaseInputTemplate, obtained from the registry. Each theme can implement its own version of BaseInputTemplate without requiring a different implementation for TextWidget.

If you wish to customize the implementation for <input>-based widgets, you can modify this template. For instance, if you have a CustomTextInput component that you want to integrate:

import { ChangeEvent, FocusEvent } from 'react';
import { getInputProps, RJSFSchema, BaseInputTemplateProps } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';

import CustomTextInput from '../CustomTextInput';

const schema: RJSFSchema = {
type: 'string',
title: 'My input',
description: 'Input description',
};

function BaseInputTemplate(props: BaseInputTemplateProps) {
const {
schema,
id,
options,
label,
value,
type,
placeholder,
required,
disabled,
readonly,
autofocus,
onChange,
onChangeOverride,
onBlur,
onFocus,
rawErrors,
hideError,
uiSchema,
registry,
formContext,
...rest
} = props;
const onTextChange = ({ target: { value: val } }: ChangeEvent<HTMLInputElement>) => {
// Use the options.emptyValue if it is specified and newVal is also an empty string
onChange(val === '' ? options.emptyValue || '' : val);
};
const onTextBlur = ({ target: { value: val } }: FocusEvent<HTMLInputElement>) => onBlur(id, val);
const onTextFocus = ({ target: { value: val } }: FocusEvent<HTMLInputElement>) => onFocus(id, val);

const inputProps = { ...rest, ...getInputProps(schema, type, options) };
const hasError = rawErrors.length > 0 && !hideError;

return (
<CustomTextInput
id={id}
label={label}
value={value}
placeholder={placeholder}
disabled={disabled}
readOnly={readonly}
autoFocus={autofocus}
error={hasError}
errors={hasError ? rawErrors : undefined}
onChange={onChangeOverride || onTextChange}
onBlur={onTextBlur}
onFocus={onTextFocus}
{...inputProps}
/>
);
}

render(
<Form schema={schema} validator={validator} templates={{ BaseInputTemplate }} />,
document.getElementById('app'),
);

In some cases, you may need to pass additional properties to the existing BaseInputTemplate. The approach varies based on whether you are using the core or some other theme, such as mui:

import { BaseInputTemplateProps } from '@rjsf/utils';
import { getDefaultRegistry } from '@rjsf/core';
import { Templates } from '@rjsf/mui';

const {
templates: { BaseInputTemplate },
} = getDefaultRegistry(); // To get templates from core
// const { BaseInputTemplate } = Templates; // To get templates from a theme do this

function MyBaseInputTemplate(props: BaseInputTemplateProps) {
const customProps = {};
// get your custom props from where you need to
return <BaseInputTemplate {...props} {...customProps} />;
}

For more detailed documentation, you can refer to React JSONSchema Form Docs.