5.x Upgrade Guide
Breaking changes
There were several significant breaking changes in RJSF version 5 that were necessary in order to support the following new features:
- Schema validation was decoupled from
@rjsf/coreto resolve issue #2693. Additionally, in order to break a circular dependency in the validation refactor, the@rjsf/core/utils.jsfile was split out into its own@rjsf/utilspackage as was suggested in #1655. - The theme for Material UI version 5 (i.e.
@rjsf/mui) was split out of the theme for version 4 (i.e.@rjsf/material-ui) to resolve the following issues: #2762, #2858, #2905, #2945 - As part of the fix for #2526 all the existing templates in the previous version were moved into a new
templatesdictionary, similar to howwidgetsandfieldswork. Thistemplatesdictionary was added to theRegistryand also theFormprops, replacing theArrayFieldTemplate,FieldTemplate,ObjectFieldTemplateandErrorListprops. In addition, several of thefieldsandwidgetsbased components were moved into thetemplatesdictionary as they were more like templates than trueFields orWidgets. Also fixes #2945 - Fixed
anyOfandoneOfgetting incorrect, potentially duplicate ids when combined with array #2197 CheckboxesWidget,RadioWidgetandSelectWidgetin all themes now use indexes intoenumOptions[]rather thanoption.valueto allow for objects inenumOptions- #1494
Node support
Version 5 is dropping official support for Node 12 as it is no longer a maintained version.
Please use Node 18 when making any changes to package.json and package-lock.json files.
All PR and branch builds are running against Node 14, 16 and 18.
React version
RJSF is no longer actively supporting React version < 16.14.x. React 17 is officially supported on all the themes where the underlying theme library also supports React 17.
Unfortunately, there is required work pending to properly support React 18, so use it at your own risk.
New packages
There are four new packages added in RJSF version 5:
@rjsf/utils: All of the utility functions previously imported from@rjsf/core/utilsas well as the Typescript types for RJSF version 5.- The following new utility functions were added:
ariaDescribedByIds(),createSchemaUtils(),descriptionId(),enumOptionsDeselectValue(),enumOptionsIndexForValue(),enumOptionsIsSelected(),enumOptionsSelectValue(),enumOptionsValueForIndex(),errorId(),examplesId(),getClosestMatchingOption(),getFirstMatchingOption(),getInputProps(),helpId(),mergeValidationData(),optionId(),sanitizeDataForNewSchema()andtitleId()
- The following new utility functions were added:
@rjsf/validator-ajv6: The ajv-v6-based validator refactored out of@rjsf/core@4.x, that implements theValidatorTypeinterface defined in@rjsf/utils.@rjsf/validator-ajv8: The ajv-v8-based validator that is an upgrade of the@rjsf/validator-ajv6, that implements theValidatorTypeinterface defined in@rjsf/utils. See the ajv 6 to 8 migration guide for more information.@rjsf/mui: Previously@rjsf/material-ui/v5, now provided as its own theme.
id BREAKING CHANGES
In many of the themes the ids for the Title, Description and Examples blocks were update to all have a consistent value of xxx__title, xxx__description and xxx__examples, respectively, where xxx is the id of the field.
In addition, some of the ids for various input values were updated to be consistent across themes or to fix small bugs.
For instance, the values for radio buttons in the RadioWidget and checkboxes in the CheckboxesWidget are of the form xxx-${option.value}, where xxx is the id of the field.
enumOptions[] widgets BREAKING CHANGES
Schemas may have enumOptions[] where values are objects rather than primitive types. Examples of such schemas have been added to the playground. These schemas did not work in RJSF prior to v5.
In every theme, the enumOptions[] rendering widgets CheckboxesWidget, RadioWidget and SelectWidget previously used the enumOptions[].value to as the value used for the underlying checkbox, radio and select.option elements.
Now, these CheckboxesWidget, RadioWidget and SelectWidget components use the index of the enumOptions[] in the list as the value for the underlying elements.
If you need to build a custom widget for this kind of enumOptions, there are a set of enumOptionsXXX functions in @rjsf/utils to support your implementation.
@rjsf/core BREAKING CHANGES
Types
In version 4, RJSF exported all its types directly from @rjsf/core.
In version 5, only the types for the Form component and the withTheme() HOC are exported directly from @rjsf/core.
All the rest of the types for RJSF are now exported from the new @rjsf/utils package.
NOTE: The types in @rjsf/utils have been improved significantly from those in version 4 and as a result may require you to fix your Typescript typings and add some casts.
Some of the most notable changes are:
-
RJSFSchemahas replaced the use ofJSON7Schemafor future compatibility reasons.- Currently
RJSFSchemais simply an alias toJSON7Schemaso this change is purely a naming one. - It is highly recommended to update your use of
JSON7SchemawithRJSFSchemaso that when the RJSF begins supporting a newer JSON Schema version out-of-the-box, your code won't be affected.
- Currently
-
RJSFSchemaDefinitionhas replaced the use ofJSONSchema7Definitionfor the same reasons. -
The use of the generic
T(defaulting toany) for theformDatatype has been expanded to cover all type hierarchies that useformData. -
StrictRJSFSchemaandRJSFSchemahave replaced the use ofJSON7Schemafor future compatibility reasons.RJSFSchemaisStrictRJSFSchemajoined with theGenericObjectType(i.e.{ [key: string]: any }) to allow for additional syntax related to newer draft versions- All definitions of
schemaandrootSchemaelements have been replaced with a generic that is defined asS extends StrictRJSFSchema = RJSFSchema - It is highly recommended to update your use of
JSON7SchemawithRJSFSchemasince that is the default for the new generic used forschemaandrootSchema
-
A new generic
F(extendingFormContextTypedefaulting toany) was added for theformContexttype, and all types in the hierarchy that useformContexthave had that generic added to them. -
The new
CustomValidator,ErrorTransformer,ValidationData,ValidatorTypeandSchemaUtilsTypetypes were added to support the decoupling of the validation implementation. -
The new
TemplatesType,ArrayFieldDescriptionProps,ArrayFieldTitleProps,UnsupportedFieldProps,IconButtonProps,SubmitButtonPropsandUIOptionsBaseTypewere added to support the consolidation (and expansion) oftemplatesin theRegistryandForm. -
BREAKING CHANGE The
DescriptionFieldandTitleFieldprops were removed from theArrayFieldTemplatePropsandObjectFieldTemplatePropsas they can now be derived from thetemplatesoruiSchemavia the newgetTemplate()utility function. -
BREAKING CHANGE The
fieldsprop was removed from theFieldTemplatePropsas you can simply useregistry.fieldsinstead. -
BREAKING CHANGE The
showErrorListprop was changed to acceptfalse,"top"or"bottom".trueis no longer a valid value. The default value is"top", which has identical behavior to the default value/truein v4. You can view all these types on GitHub.
Form props
In version 5, the Form component's two optional props additionalMetaSchemas and customFormats were replaced with the new, required validator prop, in order to support the decoupling of the validation implementation.
This new validator prop is expected to be an implementation of the ValidatorType interface.
The new @rjsf/validator-ajv6 package contains the refactored implementation of the version 4 validator; It was provided for backwards compatibility with RJSF v4, and it is deprecated.
The new @rjsf/validator-ajv8 package contains the refactored implementation of the version 4 validator, that has been converted to use the Ajv 8 validator and has more capabilities than the Ajv 6 one. See the Ajv migration guide for more information.
There are two ways to use this new package to provide a validator for a Form.
First, you can simply import the default validator from the package and pass it to a Form.
import { RJSFSchema } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";
// Your schema
const schema: RJSFSchema = { ... };
render((
<Form schema={schema} validator={validator} />
), document.getElementById("app"));
Second, if you were actually providing one (or both) of the removed optional props to your Form, you can continue using them by creating a customized validator.
import { RJSFSchema } from "@rjsf/utils";
import Form from "@rjsf/core";
import { customizeValidator, CustomValidatorOptionsType } from "@rjsf/validator-ajv8";
// Your schema, additionalMetaSchemas and/or customFormats
const schema: RJSFSchema = { ... };
const additionalMetaSchemas: CustomValidatorOptionsType['additionalMetaSchemas'] = [{ ... }];
const customFormats: CustomValidatorOptionsType['customFormats'] = { ... };
const validator = customizeValidator({ additionalMetaSchemas, customFormats });
render((
<Form schema={schema} validator={validator} />
), document.getElementById("app"));
formElement converted to RefObject
The formElement variable that stored the ref to the inner <form /> was converted from a simple variable assigned via a callback ref (ala React < 16.3) to a React.RefObject created using the React.createRef() API.
As a result, if you were using the formElement ref, you will need to update it to use formElement.current:
import { RJSFSchema } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";
// Your schema
const schema: RJSFSchema = { ... };
const formRef = React.createRef();
render((
<Form ref={formRef} schema={schema} validator={validator} />
), document.getElementById("app"));
...
// Previously, to reset the form one would have called:
// formRef.current.formElement.reset();
// Now one calls:
formRef.current.formElement.current.reset();
validate prop renamed
Additionally, in version 5, the validate prop on Form was renamed to customValidate to avoid confusion with the new validator prop.
fields prop changes
In previous versions, it was possible to provide an override to the DescriptionField, TitleField and/or UnsupportedField components by providing a custom implementation in the fields prop on the Form.
Since these components do not actually support the FieldProps interface, they were moved into the templates dictionary instead.
If you were previously overriding any (or all) of these components, you can override them now via the templates prop on Form instead:
import { DescriptionFieldProps, RJSFSchema, TitleFieldProps } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";
// Your schema
const schema: RJSFSchema = { ... };
// Your custom fields
const CustomDescriptionField = (props: DescriptionFieldProps) => { ... };
const CustomTitleField = (props: TitleFieldProps) => { ... };
const CustomUnsupportedField = (props: ObjectFieldTemplateProps) => { ...
};
const templates: Partial<TemplatesType> = {
DescriptionFieldTemplate: CustomDescriptionField,
TitleFieldTemplate: CustomTitleField,
UnsupportedFieldTemplate: CustomUnsupportedField,
};
render((
<Form schema={schema} validator={validator} templates={templates}/>
), document.getElementById("app"));
new templates prop
Additionally, in version 5, the ArrayFieldTemplate, FieldTemplate, ObjectFieldTemplate and ErrorList props were replaced with the templates prop as part of the TemplatesType consolidation.
If you were previously overriding any (or all) of these templates, you can simply consolidate them into the new templates prop on Form instead:
import { ArrayFieldTemplateProps, ErrorListProps, FieldTemplateProps, ObjectFieldTemplateProps, RJSFSchema } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";
// Your schema
const schema: RJSFSchema = { ... };
// Your custom templates
const CustomArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { ... };
const CustomFieldTemplate = (props: FieldTemplateProps) => { ... };
const CustomObjectFieldTemplate = (props: ObjectFieldTemplateProps) => { ... };
const CustomErrorField = (props: ErrorListProps) => { ... };
const templates: Partial<TemplatesType> = {
ArrayFieldTemplate: CustomArrayFieldTemplate,
FieldTemplate: CustomFieldTemplate,
ObjectFieldTemplate: CustomObjectFieldTemplate,
ErrorFieldTemplate: CustomErrorField,
};
render((
<Form schema={schema} validator={validator} templates={templates} />
), document.getElementById("app"));
NOTE: In version 5, the ArrayField implementation was refactored to add 3 additional templates for presenting arrays along with the ArrayFieldTemplate.
If you were updating the ArrayFieldTemplate to modify just a subset of the UI, it may be easier for you to implement one of the other new templates instead.
See the Custom Templates documentation for more details.
widgets prop change
In the previous version, it was possible to provide an override to the SubmitButton component by providing a custom implementation in the widgets prop on the Form.
Since this component only requires a tiny fraction of the WidgetProps interface, it was moved into the templates.ButtonTemplates dictionary instead with its own, reduced set of props.
If you were previously overriding this component, you can override it now via the templates prop on Form instead:
import { RJSFSchema, SubmitButtonProps } from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";
// Your schema
const schema: RJSFSchema = { ... };
// Your custom button
const CustomSubmitButton = (props: SubmitButtonProps) => { ...
};
const templates: Partial<TemplatesType> = {
ButtonTemplates: {
SubmitButton: CustomSubmitButton,
}
};
render((
<Form schema={schema} validator={validator} templates={templates}/>
), document.getElementById("app"));
utils.js
In version 5, all the utility functions that were previously accessed via import { utils } from '@rjsf/core'; are now available via import utils from '@rjsf/utils';.
Because of the decoupling of validation from @rjsf/core there is a breaking change for all the validator-based utility functions, since they now require an additional ValidatorType parameter.
More over, one previously exported function resolveSchema() is no longer exposed in the @rjsf/utils, so use retrieveSchema() instead.
Finally, the function getMatchingOption() has been deprecated in favor of getFirstMatchingOption().
If you have built custom fields or widgets that utilized any of these breaking-change functions, don't worry, there is a quick and easy solution for you.
The registry has a breaking-change which removes the previously deprecated definitions property while adding the new schemaUtils property.
This new registry.schemaUtils property implements the SchemaUtilsType interface, which allows you to call a version of each of these breaking-change functions without the need for passing either a validator or rootSchema.
Because all fields and widgets are guaranteed to be passed the registry as a prop, if your custom field/widget happens to use either the registry.definitions object or a breaking-change validator-based utility function you make the following changes:
import { RJSFSchema, FieldProps } from '@rjsf/utils';
function YourField(props: FieldProps) {
const { registry } = props;
// Change `registry.definitions` to `registry.rootSchema.definitions`
// const { definitions } = registry; <- version 4
const { rootSchema } = registry;
const { definitions } = rootSchema;
...
}
// Change breaking-change function to schemaUtils instead, otherwise import from @rjsf/utils
// import { utils } from '@rjsf/core'; <- version 4
// const { isMultiSelect, resolveSchema, getUiOptions } = utils; <- version 4
import { RJSFSchema, WidgetProps, getUiOptions } from '@rjsf/utils';
function YourWidget(props: WidgetProps) {
const { registry, uiSchema } = props;
const { schemaUtils } = registry;
// const matchingOption = getMatchingOption({}, options, rootSchema); <- version 4
// const isMultiSelect = isMultiSelect(schema, rootSchema); <- version 4
// const newSchema = resolveSchema(schema, formData, rootSchema); <- version 4
const matchingOption = schemaUtils.getFirstMatchingOption({}, options);
const isMultiSelect = schemaUtils.isMultiSelect(schema);
const newSchema: RJSFSchema = schemaUtils.retrieveSchema(schema, formData);
const options = getUiOptions(uiSchema);
...
}
validator.js
Because of the decoupling of validation from @rjsf/core this file was refactored into its own @rjsf/validator-ajv8 package.
During that refactor a few breaking changes were made to how it works related to custom validation and ErrorSchema conversion.
toErrorList param changed
In previous versions, the toErrorList() function used to take a fieldName string defaulted to root, and used it to format the stack message.
In version 5, fieldName was changed to fieldPath string array defaulted to an empty array, and is used to recursively add the field name to the errors as the property object along with the raw message.
The result is that if you had an ErrorSchema that looks like:
const errorSchema: ErrorSchema = {
__errors: ['error message 1'],
password: { __errors: 'passwords do not match' },
};
The returned result from calling toErrorList(errorSchema) has changed as follows:
// version 4 result
[{ stack: 'root: error message 1' }, { stack: 'password: passwords do not match' }][
// version 5 result
({ property: '.', message: 'error message 1', stack: '. error message 1' },
{
property: '.password',
message: 'passwords do not match',
stack: '.password passwords do not match',
})
];
Custom validation and extraErrors
In previous versions, when using a custom validator on the Form, any errors that were generated were inconsistently inserted into the validations errors list.
In addition, there was an issue where the non-stack AJV error information was lost when custom validation generated errors.
This issue has been fixed.
Also, when extraErrors were provided, they were being inconsistently inserted into the errors list and the non-stack AJV error information was lost.
In version 5, all of these errors will be consistently appended onto the end of the validation errors list, and the additional AJV error information is maintained.
In other words, if custom validation or extraErrors produced the following ErrorSchema:
{
__errors: [ "Please correct your password"],
password2: { __errors: "passwords do not match" }
}
And the AJV validation produced the following ErrorSchema:
{
__errors: [
{
message: 'should NOT be shorter than 3 characters',
name: 'minLength',
params: { limit: 3 },
property: '.password2',
schemaPath: '#/properties/password2/minLength',
stack: '.password2 should NOT be shorter than 3 characters',
},
];
}
The resulting errors list has changed as follows:
// version 4
[
{ stack: 'root: Please correct your password' },
{ stack: 'password2: passwords do not match' },
{ stack: 'password2: should NOT be shorter than 3 characters' },
][
// version 5
({
message: 'should NOT be shorter than 3 characters',
name: 'minLength',
params: { limit: 3 },
property: '.password2',
schemaPath: '#/properties/password2/minLength',
stack: '.password2 should NOT be shorter than 3 characters',
},
{
property: '.',
message: 'Please correct your password',
stack: '. Please correct your password',
},
{
property: '.',
message: 'passwords do not match',
stack: '.password2 passwords do not match',
})
];
Generate correct ids when arrays are combined with anyOf/oneOf
In v4, with arrays inside anyOf or oneOf, the parent name was not used to generate ids.
For example, given a schema such as:
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"anyOf": [
{
"properties": {
"foo": {
"type": "string"
}
}
},
{
"properties": {
"bar": {
"type": "string"
}
}
}
]
}
}
}
}
We would get fields with id root_foo and root_bar.
As you can imagine, we could end up with duplicated ids if there's actually a foo or a bar in the root of the schema.
From v5, the child fields will correctly use the parent id when generating its own id, generating ids such as root_items_0_foo.
Deprecations added in v5
getMatchingOption()
The utility function getMatchingOption() was deprecated in favor of the more aptly named getFirstMatchingOption() which has the exact same implementation.
Non-standard enumNames property
enumNames is a non-standard JSON Schema field that was deprecated in version 5.
enumNames could be included in the schema to apply labels that differed from an enumeration value.
This behavior can still be accomplished with oneOf or anyOf containing const values, so enumNames support will be removed from a future major version of RJSF.
For more information, see #532.
uiSchema.classNames
In versions previous to 5, uiSchema.classNames was the only property that did not require the ui: prefix.
Additionally, it did not support being added into the ui:options object.
This was fixed in version 5 to be consistent with all the other properties in the uiSchema, so the uiSchema.classNames support may be removed from a future major version of RJSF.
If you are using classNames as follows, simply add the ui: prefix to it to remove the deprecation warning that will be displayed for each uiSchema.classNames you have:
import { UiSchema } from '@rjsf/utils';
// This uiSchema will log a deprecation warning to the console
const uiSchemaLog: UiSchema = {
title: {
classNames: 'myClass',
},
};
// This uiSchema will not
const uiSchemaNoLog: UiSchema = {
title: {
'ui:classNames': 'myClass',
},
};
@rjsf/material-ui BREAKING CHANGES
This theme was simplified back to working only with Material UI version 4 after an unsuccessful attempt to have it fully support both versions 4 and 5 simultaneously.
As a result, the MuiComponentContext, MuiForm5, Theme5 components and the useMuiComponent hook were removed from the export.
In addition, the /v4 and /v5 sub-packages were also removed.
Migrating for Material UI version 4
If you were using this theme for Material UI version 4 AND you were using the sub-package, simply remove the /v4 from your imports.
If you modified your Typescript configuration for the /v4 sub-package, remove the following from your tsconfig.json:
{
...
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@rjsf/material-ui/*": ["node_modules/@rjsf/material-ui/dist/*"]
}
}
}
If you modified your Jest configuration for the /v4 sub-package, remove the following from your jest.config.json:
"moduleNameMapper": {
"@rjsf/material-ui/v4": "<rootDir>/node_modules/@rjsf/material-ui/dist/v4.js"
},
Migrating for Material UI version 5
If you were using this theme for Material UI version 5, you will want to use @rjsf/mui instead.
See below for some before and after examples.
If you modified your Typescript configuration for the /v5 sub-package, remove the following from your tsconfig.json:
{
...
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@rjsf/material-ui/*": ["node_modules/@rjsf/material-ui/dist/*"]
}
}
}
If you modified your Jest configuration for the /v5 sub-package, remove the following from your jest.config.json:
"moduleNameMapper": {
"@rjsf/material-ui/v5": "<rootDir>/node_modules/@rjsf/material-ui/dist/v5.js"
},
Before
import Form5 from '@rjsf/material-ui';
or
import Form from '@rjsf/material-ui/v5';
or
import { withTheme } from '@rjsf/core';
import { Theme } from '@rjsf/material-ui/v5';
// Make modifications to the theme with your own fields and widgets
const Form = withTheme(Theme);
or
import { withTheme } from '@rjsf/core';
import { Theme as Theme5 } from '@rjsf/material-ui';
// Make modifications to the theme with your own fields and widgets
const Form = withTheme(Theme5);
After
import Form from '@rjsf/mui';
or
import { withTheme } from '@rjsf/core';
import { Theme } from '@rjsf/mui';
// Make modifications to the theme with your own fields and widgets
const Form = withTheme(Theme);