UNPKG

@rjsf/core

Version:

A simple React component capable of building HTML forms out of a JSON schema.

664 lines (663 loc) 34.5 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Component, createRef } from 'react'; import { createSchemaUtils, deepEquals, getChangedFields, getTemplate, getUiOptions, isObject, mergeObjects, NAME_KEY, RJSF_ADDITIONAL_PROPERTIES_FLAG, shouldRender, SUBMIT_BTN_OPTIONS_KEY, toErrorList, UI_GLOBAL_OPTIONS_KEY, UI_OPTIONS_KEY, validationDataMerge, createErrorHandler, unwrapErrorHandler, } from '@rjsf/utils'; import _forEach from 'lodash-es/forEach.js'; import _get from 'lodash-es/get.js'; import _isEmpty from 'lodash-es/isEmpty.js'; import _isNil from 'lodash-es/isNil.js'; import _pick from 'lodash-es/pick.js'; import _toPath from 'lodash-es/toPath.js'; import getDefaultRegistry from '../getDefaultRegistry.js'; /** The `Form` component renders the outer form and all the fields defined in the `schema` */ export default class Form extends Component { /** The ref used to hold the `form` element, this needs to be `any` because `tagName` or `_internalFormWrapper` can * provide any possible type here */ formElement; /** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the * `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the * state construction. * * @param props - The initial props for the `Form` */ constructor(props) { super(props); if (!props.validator) { throw new Error('A validator is required for Form functionality to work'); } this.state = this.getStateFromProps(props, props.formData); if (this.props.onChange && !deepEquals(this.state.formData, this.props.formData)) { this.props.onChange(this.state); } this.formElement = createRef(); } /** * `getSnapshotBeforeUpdate` is a React lifecycle method that is invoked right before the most recently rendered * output is committed to the DOM. It enables your component to capture current values (e.g., scroll position) before * they are potentially changed. * * In this case, it checks if the props have changed since the last render. If they have, it computes the next state * of the component using `getStateFromProps` method and returns it along with a `shouldUpdate` flag set to `true` IF * the `nextState` and `prevState` are different, otherwise `false`. This ensures that we have the most up-to-date * state ready to be applied in `componentDidUpdate`. * * If `formData` hasn't changed, it simply returns an object with `shouldUpdate` set to `false`, indicating that a * state update is not necessary. * * @param prevProps - The previous set of props before the update. * @param prevState - The previous state before the update. * @returns Either an object containing the next state and a flag indicating that an update should occur, or an object * with a flag indicating that an update is not necessary. */ getSnapshotBeforeUpdate(prevProps, prevState) { if (!deepEquals(this.props, prevProps)) { const formDataChangedFields = getChangedFields(this.props.formData, prevProps.formData); const isSchemaChanged = !deepEquals(prevProps.schema, this.props.schema); // When formData is not an object, getChangedFields returns an empty array. // In this case, deepEquals is most needed to check again. const isFormDataChanged = formDataChangedFields.length > 0 || !deepEquals(prevProps.formData, this.props.formData); const nextState = this.getStateFromProps(this.props, this.props.formData, // If the `schema` has changed, we need to update the retrieved schema. // Or if the `formData` changes, for example in the case of a schema with dependencies that need to // match one of the subSchemas, the retrieved schema must be updated. isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema, isSchemaChanged, formDataChangedFields); const shouldUpdate = !deepEquals(nextState, prevState); return { nextState, shouldUpdate }; } return { shouldUpdate: false }; } /** * `componentDidUpdate` is a React lifecycle method that is invoked immediately after updating occurs. This method is * not called for the initial render. * * Here, it checks if an update is necessary based on the `shouldUpdate` flag received from `getSnapshotBeforeUpdate`. * If an update is required, it applies the next state and, if needed, triggers the `onChange` handler to inform about * changes. * * This method effectively replaces the deprecated `UNSAFE_componentWillReceiveProps`, providing a safer alternative * to handle prop changes and state updates. * * @param _ - The previous set of props. * @param prevState - The previous state of the component before the update. * @param snapshot - The value returned from `getSnapshotBeforeUpdate`. */ componentDidUpdate(_, prevState, snapshot) { if (snapshot.shouldUpdate) { const { nextState } = snapshot; if (!deepEquals(nextState.formData, this.props.formData) && !deepEquals(nextState.formData, prevState.formData) && this.props.onChange) { this.props.onChange(nextState); } this.setState(nextState); } } /** Extracts the updated state from the given `props` and `inputFormData`. As part of this process, the * `inputFormData` is first processed to add any missing required defaults. After that, the data is run through the * validation process IF required by the `props`. * * @param props - The props passed to the `Form` * @param inputFormData - The new or current data for the `Form` * @param retrievedSchema - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`. * @param isSchemaChanged - A flag indicating whether the schema has changed. * @param formDataChangedFields - The changed fields of `formData` * @returns - The new state for the `Form` */ getStateFromProps(props, inputFormData, retrievedSchema, isSchemaChanged = false, formDataChangedFields = []) { const state = this.state || {}; const schema = 'schema' in props ? props.schema : this.props.schema; const uiSchema = ('uiSchema' in props ? props.uiSchema : this.props.uiSchema) || {}; const edit = typeof inputFormData !== 'undefined'; const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate; const mustValidate = edit && !props.noValidate && liveValidate; const rootSchema = schema; const experimental_defaultFormStateBehavior = 'experimental_defaultFormStateBehavior' in props ? props.experimental_defaultFormStateBehavior : this.props.experimental_defaultFormStateBehavior; const experimental_customMergeAllOf = 'experimental_customMergeAllOf' in props ? props.experimental_customMergeAllOf : this.props.experimental_customMergeAllOf; let schemaUtils = state.schemaUtils; if (!schemaUtils || schemaUtils.doesSchemaUtilsDiffer(props.validator, rootSchema, experimental_defaultFormStateBehavior, experimental_customMergeAllOf)) { schemaUtils = createSchemaUtils(props.validator, rootSchema, experimental_defaultFormStateBehavior, experimental_customMergeAllOf); } const formData = schemaUtils.getDefaultFormState(schema, inputFormData); const _retrievedSchema = this.updateRetrievedSchema(retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData)); const getCurrentErrors = () => { // If the `props.noValidate` option is set or the schema has changed, we reset the error state. if (props.noValidate || isSchemaChanged) { return { errors: [], errorSchema: {} }; } else if (!props.liveValidate) { return { errors: state.schemaValidationErrors || [], errorSchema: state.schemaValidationErrorSchema || {}, }; } return { errors: state.errors || [], errorSchema: state.errorSchema || {}, }; }; let errors; let errorSchema; let schemaValidationErrors = state.schemaValidationErrors; let schemaValidationErrorSchema = state.schemaValidationErrorSchema; if (mustValidate) { const schemaValidation = this.validate(formData, schema, schemaUtils, _retrievedSchema); errors = schemaValidation.errors; // If retrievedSchema is undefined which means the schema or formData has changed, we do not merge state. // Else in the case where it hasn't changed, we merge 'state.errorSchema' with 'schemaValidation.errorSchema.' This done to display the raised field error. if (retrievedSchema === undefined) { errorSchema = schemaValidation.errorSchema; } else { errorSchema = mergeObjects(this.state?.errorSchema, schemaValidation.errorSchema, 'preventDuplicates'); } schemaValidationErrors = errors; schemaValidationErrorSchema = errorSchema; } else { const currentErrors = getCurrentErrors(); errors = currentErrors.errors; errorSchema = currentErrors.errorSchema; if (formDataChangedFields.length > 0) { const newErrorSchema = formDataChangedFields.reduce((acc, key) => { acc[key] = undefined; return acc; }, {}); errorSchema = schemaValidationErrorSchema = mergeObjects(currentErrors.errorSchema, newErrorSchema, 'preventDuplicates'); } } if (props.extraErrors) { const merged = validationDataMerge({ errorSchema, errors }, props.extraErrors); errorSchema = merged.errorSchema; errors = merged.errors; } const idSchema = schemaUtils.toIdSchema(_retrievedSchema, uiSchema['ui:rootFieldId'], formData, props.idPrefix, props.idSeparator); const nextState = { schemaUtils, schema, uiSchema, idSchema, formData, edit, errors, errorSchema, schemaValidationErrors, schemaValidationErrorSchema, retrievedSchema: _retrievedSchema, }; return nextState; } /** React lifecycle method that is used to determine whether component should be updated. * * @param nextProps - The next version of the props * @param nextState - The next version of the state * @returns - True if the component should be updated, false otherwise */ shouldComponentUpdate(nextProps, nextState) { return shouldRender(this, nextProps, nextState); } /** Gets the previously raised customValidate errors. * * @returns the previous customValidate errors */ getPreviousCustomValidateErrors() { const { customValidate, uiSchema } = this.props; const prevFormData = this.state.formData; let customValidateErrors = {}; if (typeof customValidate === 'function') { const errorHandler = customValidate(prevFormData, createErrorHandler(prevFormData), uiSchema); const userErrorSchema = unwrapErrorHandler(errorHandler); customValidateErrors = userErrorSchema; } return customValidateErrors; } /** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the * `schemaUtils` in the state), returning the results. * * @param formData - The new form data to validate * @param schema - The schema used to validate against * @param altSchemaUtils - The alternate schemaUtils to use for validation */ validate(formData, schema = this.props.schema, altSchemaUtils, retrievedSchema) { const schemaUtils = altSchemaUtils ? altSchemaUtils : this.state.schemaUtils; const { customValidate, transformErrors, uiSchema } = this.props; const resolvedSchema = retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData); return schemaUtils .getValidator() .validateFormData(formData, resolvedSchema, customValidate, transformErrors, uiSchema); } /** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */ renderErrors(registry) { const { errors, errorSchema, schema, uiSchema } = this.state; const { formContext } = this.props; const options = getUiOptions(uiSchema); const ErrorListTemplate = getTemplate('ErrorListTemplate', registry, options); if (errors && errors.length) { return (_jsx(ErrorListTemplate, { errors: errors, errorSchema: errorSchema || {}, schema: schema, uiSchema: uiSchema, formContext: formContext, registry: registry })); } return null; } /** Returns the `formData` with only the elements specified in the `fields` list * * @param formData - The data for the `Form` * @param fields - The fields to keep while filtering */ getUsedFormData = (formData, fields) => { // For the case of a single input form if (fields.length === 0 && typeof formData !== 'object') { return formData; } // _pick has incorrect type definition, it works with string[][], because lodash/hasIn supports it const data = _pick(formData, fields); if (Array.isArray(formData)) { return Object.keys(data).map((key) => data[key]); } return data; }; /** Returns the list of field names from inspecting the `pathSchema` as well as using the `formData` * * @param pathSchema - The `PathSchema` object for the form * @param [formData] - The form data to use while checking for empty objects/arrays */ getFieldNames = (pathSchema, formData) => { const getAllPaths = (_obj, acc = [], paths = [[]]) => { Object.keys(_obj).forEach((key) => { if (typeof _obj[key] === 'object') { const newPaths = paths.map((path) => [...path, key]); // If an object is marked with additionalProperties, all its keys are valid if (_obj[key][RJSF_ADDITIONAL_PROPERTIES_FLAG] && _obj[key][NAME_KEY] !== '') { acc.push(_obj[key][NAME_KEY]); } else { getAllPaths(_obj[key], acc, newPaths); } } else if (key === NAME_KEY && _obj[key] !== '') { paths.forEach((path) => { const formValue = _get(formData, path); // adds path to fieldNames if it points to a value // or an empty object/array if (typeof formValue !== 'object' || _isEmpty(formValue) || (Array.isArray(formValue) && formValue.every((val) => typeof val !== 'object'))) { acc.push(path); } }); } }); return acc; }; return getAllPaths(pathSchema); }; /** Returns the `formData` after filtering to remove any extra data not in a form field * * @param formData - The data for the `Form` * @returns The `formData` after omitting extra data */ omitExtraData = (formData) => { const { schema, schemaUtils } = this.state; const retrievedSchema = schemaUtils.retrieveSchema(schema, formData); const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData); const fieldNames = this.getFieldNames(pathSchema, formData); const newFormData = this.getUsedFormData(formData, fieldNames); return newFormData; }; // Filtering errors based on your retrieved schema to only show errors for properties in the selected branch. filterErrorsBasedOnSchema(schemaErrors, resolvedSchema, formData) { const { retrievedSchema, schemaUtils } = this.state; const _retrievedSchema = resolvedSchema ?? retrievedSchema; const pathSchema = schemaUtils.toPathSchema(_retrievedSchema, '', formData); const fieldNames = this.getFieldNames(pathSchema, formData); const filteredErrors = _pick(schemaErrors, fieldNames); // If the root schema is of a primitive type, do not filter out the __errors if (resolvedSchema?.type !== 'object' && resolvedSchema?.type !== 'array') { filteredErrors.__errors = schemaErrors.__errors; } const prevCustomValidateErrors = this.getPreviousCustomValidateErrors(); // Filtering out the previous raised customValidate errors so that they are cleared when no longer valid. const filterPreviousCustomErrors = (errors = [], prevCustomErrors) => { if (errors.length === 0) { return errors; } return errors.filter((error) => { return !prevCustomErrors.includes(error); }); }; // Removing undefined, null and empty errors. const filterNilOrEmptyErrors = (errors, previousCustomValidateErrors = {}) => { _forEach(errors, (errorAtKey, errorKey) => { const prevCustomValidateErrorAtKey = previousCustomValidateErrors[errorKey]; if (_isNil(errorAtKey) || (Array.isArray(errorAtKey) && errorAtKey.length === 0)) { delete errors[errorKey]; } else if (isObject(errorAtKey) && isObject(prevCustomValidateErrorAtKey) && Array.isArray(prevCustomValidateErrorAtKey?.__errors)) { // if previous customValidate error is an object and has __errors array, filter out the errors previous customValidate errors. errors[errorKey] = filterPreviousCustomErrors(errorAtKey.__errors, prevCustomValidateErrorAtKey.__errors); } else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) { filterNilOrEmptyErrors(errorAtKey, previousCustomValidateErrors[errorKey]); } }); return errors; }; return filterNilOrEmptyErrors(filteredErrors, prevCustomValidateErrors); } /** Function to handle changes made to a field in the `Form`. This handler receives an entirely new copy of the * `formData` along with a new `ErrorSchema`. It will first update the `formData` with any missing default fields and * then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not * in a form field. Then, the resulting formData will be validated if required. The state will be updated with the new * updated (potentially filtered) `formData`, any errors that resulted from validation. Finally the `onChange` * callback will be called if specified with the updated state. * * @param formData - The new form data from a change to a field * @param newErrorSchema - The new `ErrorSchema` based on the field change * @param id - The id of the field that caused the change */ onChange = (formData, newErrorSchema, id) => { const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props; const { schemaUtils, schema } = this.state; let retrievedSchema = this.state.retrievedSchema; if (isObject(formData) || Array.isArray(formData)) { const newState = this.getStateFromProps(this.props, formData); formData = newState.formData; retrievedSchema = newState.retrievedSchema; } const mustValidate = !noValidate && liveValidate; let state = { formData, schema }; let newFormData = formData; if (omitExtraData === true && liveOmit === true) { newFormData = this.omitExtraData(formData); state = { formData: newFormData, }; } if (mustValidate) { const schemaValidation = this.validate(newFormData, schema, schemaUtils, retrievedSchema); let errors = schemaValidation.errors; let errorSchema = schemaValidation.errorSchema; const schemaValidationErrors = errors; const schemaValidationErrorSchema = errorSchema; if (extraErrors) { const merged = validationDataMerge(schemaValidation, extraErrors); errorSchema = merged.errorSchema; errors = merged.errors; } // Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors. if (newErrorSchema) { const filteredErrors = this.filterErrorsBasedOnSchema(newErrorSchema, retrievedSchema, newFormData); errorSchema = mergeObjects(errorSchema, filteredErrors, 'preventDuplicates'); } state = { formData: newFormData, errors, errorSchema, schemaValidationErrors, schemaValidationErrorSchema, }; } else if (!noValidate && newErrorSchema) { const errorSchema = extraErrors ? mergeObjects(newErrorSchema, extraErrors, 'preventDuplicates') : newErrorSchema; state = { formData: newFormData, errorSchema: errorSchema, errors: toErrorList(errorSchema), }; } this.setState(state, () => onChange && onChange({ ...this.state, ...state }, id)); }; /** * If the retrievedSchema has changed the new retrievedSchema is returned. * Otherwise, the old retrievedSchema is returned to persist reference. * - This ensures that AJV retrieves the schema from the cache when it has not changed, * avoiding the performance cost of recompiling the schema. * * @param retrievedSchema The new retrieved schema. * @returns The new retrieved schema if it has changed, else the old retrieved schema. */ updateRetrievedSchema(retrievedSchema) { const isTheSame = deepEquals(retrievedSchema, this.state?.retrievedSchema); return isTheSame ? this.state.retrievedSchema : retrievedSchema; } /** * Callback function to handle reset form data. * - Reset all fields with default values. * - Reset validations and errors * */ reset = () => { const { onChange } = this.props; const newState = this.getStateFromProps(this.props, undefined); const newFormData = newState.formData; const state = { formData: newFormData, errorSchema: {}, errors: [], schemaValidationErrors: [], schemaValidationErrorSchema: {}, }; this.setState(state, () => onChange && onChange({ ...this.state, ...state })); }; /** Callback function to handle when a field on the form is blurred. Calls the `onBlur` callback for the `Form` if it * was provided. * * @param id - The unique `id` of the field that was blurred * @param data - The data associated with the field that was blurred */ onBlur = (id, data) => { const { onBlur } = this.props; if (onBlur) { onBlur(id, data); } }; /** Callback function to handle when a field on the form is focused. Calls the `onFocus` callback for the `Form` if it * was provided. * * @param id - The unique `id` of the field that was focused * @param data - The data associated with the field that was focused */ onFocus = (id, data) => { const { onFocus } = this.props; if (onFocus) { onFocus(id, data); } }; /** Callback function to handle when the form is submitted. First, it prevents the default event behavior. Nothing * happens if the target and currentTarget of the event are not the same. It will omit any extra data in the * `formData` in the state if `omitExtraData` is true. It will validate the resulting `formData`, reporting errors * via the `onError()` callback unless validation is disabled. Finally, it will add in any `extraErrors` and then call * back the `onSubmit` callback if it was provided. * * @param event - The submit HTML form event */ onSubmit = (event) => { event.preventDefault(); if (event.target !== event.currentTarget) { return; } event.persist(); const { omitExtraData, extraErrors, noValidate, onSubmit } = this.props; let { formData: newFormData } = this.state; if (omitExtraData === true) { newFormData = this.omitExtraData(newFormData); } if (noValidate || this.validateFormWithFormData(newFormData)) { // There are no errors generated through schema validation. // Check for user provided errors and update state accordingly. const errorSchema = extraErrors || {}; const errors = extraErrors ? toErrorList(extraErrors) : []; this.setState({ formData: newFormData, errors, errorSchema, schemaValidationErrors: [], schemaValidationErrorSchema: {}, }, () => { if (onSubmit) { onSubmit({ ...this.state, formData: newFormData, status: 'submitted' }, event); } }); } }; /** Returns the registry for the form */ getRegistry() { const { translateString: customTranslateString, uiSchema = {} } = this.props; const { schemaUtils } = this.state; const { fields, templates, widgets, formContext, translateString } = getDefaultRegistry(); return { fields: { ...fields, ...this.props.fields }, templates: { ...templates, ...this.props.templates, ButtonTemplates: { ...templates.ButtonTemplates, ...this.props.templates?.ButtonTemplates, }, }, widgets: { ...widgets, ...this.props.widgets }, rootSchema: this.props.schema, formContext: this.props.formContext || formContext, schemaUtils, translateString: customTranslateString || translateString, globalUiOptions: uiSchema[UI_GLOBAL_OPTIONS_KEY], }; } /** Provides a function that can be used to programmatically submit the `Form` */ submit = () => { if (this.formElement.current) { const submitCustomEvent = new CustomEvent('submit', { cancelable: true, }); submitCustomEvent.preventDefault(); this.formElement.current.dispatchEvent(submitCustomEvent); this.formElement.current.requestSubmit(); } }; /** Attempts to focus on the field associated with the `error`. Uses the `property` field to compute path of the error * field, then, using the `idPrefix` and `idSeparator` converts that path into an id. Then the input element with that * id is attempted to be found using the `formElement` ref. If it is located, then it is focused. * * @param error - The error on which to focus */ focusOnError(error) { const { idPrefix = 'root', idSeparator = '_' } = this.props; const { property } = error; const path = _toPath(property); if (path[0] === '') { // Most of the time the `.foo` property results in the first element being empty, so replace it with the idPrefix path[0] = idPrefix; } else { // Otherwise insert the idPrefix into the first location using unshift path.unshift(idPrefix); } const elementId = path.join(idSeparator); let field = this.formElement.current.elements[elementId]; if (!field) { // if not an exact match, try finding an input starting with the element id (like radio buttons or checkboxes) field = this.formElement.current.querySelector(`input[id^="${elementId}"`); } if (field && field.length) { // If we got a list with length > 0 field = field[0]; } if (field) { field.focus(); } } /** Validates the form using the given `formData`. For use on form submission or on programmatic validation. * If `onError` is provided, then it will be called with the list of errors. * * @param formData - The form data to validate * @returns - True if the form is valid, false otherwise. */ validateFormWithFormData = (formData) => { const { extraErrors, extraErrorsBlockSubmit, focusOnFirstError, onError } = this.props; const { errors: prevErrors } = this.state; const schemaValidation = this.validate(formData); let errors = schemaValidation.errors; let errorSchema = schemaValidation.errorSchema; const schemaValidationErrors = errors; const schemaValidationErrorSchema = errorSchema; const hasError = errors.length > 0 || (extraErrors && extraErrorsBlockSubmit); if (hasError) { if (extraErrors) { const merged = validationDataMerge(schemaValidation, extraErrors); errorSchema = merged.errorSchema; errors = merged.errors; } if (focusOnFirstError) { if (typeof focusOnFirstError === 'function') { focusOnFirstError(errors[0]); } else { this.focusOnError(errors[0]); } } this.setState({ errors, errorSchema, schemaValidationErrors, schemaValidationErrorSchema, }, () => { if (onError) { onError(errors); } else { console.error('Form validation failed', errors); } }); } else if (prevErrors.length > 0) { this.setState({ errors: [], errorSchema: {}, schemaValidationErrors: [], schemaValidationErrorSchema: {}, }); } return !hasError; }; /** Programmatically validate the form. If `omitExtraData` is true, the `formData` will first be filtered to remove * any extra data not in a form field. If `onError` is provided, then it will be called with the list of errors the * same way as would happen on form submission. * * @returns - True if the form is valid, false otherwise. */ validateForm() { const { omitExtraData } = this.props; let { formData: newFormData } = this.state; if (omitExtraData === true) { newFormData = this.omitExtraData(newFormData); } return this.validateFormWithFormData(newFormData); } /** Renders the `Form` fields inside the <form> | `tagName` or `_internalFormWrapper`, rendering any errors if * needed along with the submit button or any children of the form. */ render() { const { children, id, idPrefix, idSeparator, className = '', tagName, name, method, target, action, autoComplete, enctype, acceptcharset, acceptCharset, noHtml5Validate = false, disabled, readonly, formContext, showErrorList = 'top', _internalFormWrapper, } = this.props; const { schema, uiSchema, formData, errorSchema, idSchema } = this.state; const registry = this.getRegistry(); const { SchemaField: _SchemaField } = registry.fields; const { SubmitButton } = registry.templates.ButtonTemplates; // The `semantic-ui` and `material-ui` themes have `_internalFormWrapper`s that take an `as` prop that is the // PropTypes.elementType to use for the inner tag, so we'll need to pass `tagName` along if it is provided. // NOTE, the `as` prop is native to `semantic-ui` and is emulated in the `material-ui` theme const as = _internalFormWrapper ? tagName : undefined; const FormTag = _internalFormWrapper || tagName || 'form'; let { [SUBMIT_BTN_OPTIONS_KEY]: submitOptions = {} } = getUiOptions(uiSchema); if (disabled) { submitOptions = { ...submitOptions, props: { ...submitOptions.props, disabled: true } }; } const submitUiSchema = { [UI_OPTIONS_KEY]: { [SUBMIT_BTN_OPTIONS_KEY]: submitOptions } }; return (_jsxs(FormTag, { className: className ? className : 'rjsf', id: id, name: name, method: method, target: target, action: action, autoComplete: autoComplete, encType: enctype, acceptCharset: acceptCharset || acceptcharset, noValidate: noHtml5Validate, onSubmit: this.onSubmit, as: as, ref: this.formElement, children: [showErrorList === 'top' && this.renderErrors(registry), _jsx(_SchemaField, { name: '', schema: schema, uiSchema: uiSchema, errorSchema: errorSchema, idSchema: idSchema, idPrefix: idPrefix, idSeparator: idSeparator, formContext: formContext, formData: formData, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, registry: registry, disabled: disabled, readonly: readonly }), children ? children : _jsx(SubmitButton, { uiSchema: submitUiSchema, registry: registry }), showErrorList === 'bottom' && this.renderErrors(registry)] })); } }