Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Partially fix #3189 #3200

Merged
merged 2 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ should change the heading of the (upcoming) version to include a major version b
## @rjsf/chakra-ui
- Automatically close single-choice Select widget on selection

## @rjsf/core
- Added the new generic, `S extends StrictRJSFSchema = RJSFSchema`, for `schema`/`rootSchema` to every component that needed it.

## @rjsf/utils
- Beta-only potentially BREAKING CHANGE: Changed all types that directly or indirectly defined `schema`/`rootSchema` to add the generic `S extends StrictRJSFSchema = RJSFSchema` and use `S` as the type for them.
- `StrictRJSFSchema` was added as the alias to `JSON7Schema` and `RJSFSchema` was modified to be `StrictRJSFSchema & GenericObjectType`
- This new generic was added BEFORE the newly added `F = any` generic because it is assumed that more people will want to change the schema than the formContext types
- This provides future support for the newer draft versions of the schema

## @rjsf/validator-ajv6
- Fixed a few type casts given the new expanded definition of the `RJSFSchema` type change

## @rjsf/validator-ajv8
- Updated the typing to add the new `S extends StrictRJSFSchema = RJSFSchema` generic and fixed up type casts

## Dev / docs / playground
- Updated the `5.x upgrade guide` to document the new `StrictRJSFSchema` and `S` generic

# 5.0.0-beta.11

## @rjsf/antd
Expand Down
30 changes: 15 additions & 15 deletions docs/5.x upgrade guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ All the rest of the types for RJSF are now exported from the new `@rjsf/utils` p
NOTE: The types in `@rjsf/utils` have been improved significantly from those in version 4.
Some of the most notable changes are:

- `RJSFSchema` has replaced the use of `JSON7Schema` for future compatibility reasons.
- Currently `RJSFSchema` is simply an alias to `JSON7Schema` so this change is purely a naming one.
- It is highly recommended to update your use of `JSON7Schema` with `RJSFSchema` so that when the RJSF begins supporting a newer JSON Schema version out-of-the-box, your code won't be affected.
- `RJSFSchemaDefinition` has replaced the use of `JSONSchema7Definition` for the same reasons.
- The use of the generic `T` (defaulting to `any`) for the `formData` type has been expanded to cover all type hierarchies that use `formData`.
- `StrictRJSFSchema` and `RJSFSchema` have replaced the use of `JSON7Schema` for future compatibility reasons.
- `RJSFSchema` is `StrictRJSFSchema` joined with the `GenericObjectType` (i.e. `{ [key: string]: any }`) to allow for additional syntax related to newer draft versions
- All definitions of `schema` and `rootSchema` elements have been replaced with a generic that is defined as `S extends StrictRJSFSchema = RJSFSchema`
- It is highly recommended to update your use of `JSON7Schema` with `RJSFSchema` since that is the default for the new generic used for `schema` and `rootSchema`
- A new generic `F` (defaulting to `any`) was added for the `formContext` type, and all types in the hierarchy that use `formContext` have had that generic added to them.
- The new `CustomValidator`, `ErrorTransformer`, `ValidationData`, `ValidatorType` and `SchemaUtilsType` types were added to support the decoupling of the validation implementation.
- The new `TemplatesType`, `ArrayFieldDescriptionProps`, `ArrayFieldTitleProps`, `UnsupportedFieldProps`, `IconButtonProps`, `SubmitButtonProps` and `UIOptionsBaseType` were added to support the consolidation (and expansion) of `templates` in the `Registry` and `Form`.
Expand Down Expand Up @@ -362,21 +362,21 @@ For example, given a schema such as:
{
"properties": {
"foo": {
"type": "string",
},
},
"type": "string"
}
}
},
{
"properties": {
"bar": {
"type": "string",
},
},
},
],
},
},
},
"type": "string"
}
}
}
]
}
}
}
}
```

Expand Down
99 changes: 58 additions & 41 deletions packages/core/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IdSchema,
PathSchema,
RJSFSchema,
StrictRJSFSchema,
RJSFValidationError,
Registry,
RegistryWidgetsType,
Expand All @@ -33,15 +34,19 @@ import _isEmpty from "lodash/isEmpty";
import getDefaultRegistry from "../getDefaultRegistry";

/** The properties that are passed to the `Form` */
export interface FormProps<T = any, F = any> {
export interface FormProps<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F = any
> {
/** The JSON schema object for the form */
schema: RJSFSchema;
schema: S;
/** An implementation of the `ValidatorType` interface that is needed for form validation to work */
validator: ValidatorType<T>;
validator: ValidatorType<T, S>;
/** The optional children for the form, if provided, it will replace the default `SubmitButton` */
children?: React.ReactNode;
/** The uiSchema for the form */
uiSchema?: UiSchema<T, F>;
uiSchema?: UiSchema<T, S, F>;
/** The data for the form, used to prefill a form with existing data */
formData?: T;
// Form presentation and behavior modifiers
Expand Down Expand Up @@ -71,19 +76,19 @@ export interface FormProps<T = any, F = any> {
readonly?: boolean;
// Form registry
/** The dictionary of registered fields in the form */
fields?: RegistryFieldsType<T, F>;
fields?: RegistryFieldsType<T, S, F>;
/** The dictionary of registered templates in the form; Partial allows a subset to be provided beyond the defaults */
templates?: Partial<Omit<TemplatesType<T, F>, "ButtonTemplates">> & {
ButtonTemplates?: Partial<TemplatesType<T, F>["ButtonTemplates"]>;
templates?: Partial<Omit<TemplatesType<T, S, F>, "ButtonTemplates">> & {
ButtonTemplates?: Partial<TemplatesType<T, S, F>["ButtonTemplates"]>;
};
/** The dictionary of registered widgets in the form */
widgets?: RegistryWidgetsType<T, F>;
widgets?: RegistryWidgetsType<T, S, F>;
// Callbacks
/** If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will
* receive the same args as `onSubmit` any time a value is updated in the form. Can also return the `id` of the field
* that caused the change
*/
onChange?: (data: IChangeEvent<T, F>, id?: string) => void;
onChange?: (data: IChangeEvent<T, S, F>, id?: string) => void;
/** To react when submitted form data are invalid, pass an `onError` handler. It will be passed the list of
* encountered errors
*/
Expand All @@ -92,7 +97,7 @@ export interface FormProps<T = any, F = any> {
* and its data are valid. It will be passed a result object having a `formData` attribute, which is the valid form
* data you're usually after. The original event will also be passed as a second parameter
*/
onSubmit?: (data: IChangeEvent<T, F>, event: React.FormEvent<any>) => void;
onSubmit?: (data: IChangeEvent<T, S, F>, event: React.FormEvent<any>) => void;
/** Sometimes you may want to trigger events or modify external state when a field has been touched, so you can pass
* an `onBlur` handler, which will receive the id of the input that was blurred and the field value
*/
Expand Down Expand Up @@ -184,17 +189,21 @@ export interface FormProps<T = any, F = any> {
}

/** The data that is contained within the state for the `Form` */
export interface FormState<T = any, F = any> {
export interface FormState<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F = any
> {
/** The JSON schema object for the form */
schema: RJSFSchema;
schema: S;
/** The uiSchema for the form */
uiSchema: UiSchema<T, F>;
uiSchema: UiSchema<T, S, F>;
/** The `IdSchema` for the form, computed from the `schema`, the `rootFieldId`, the `formData` and the `idPrefix` and
* `idSeparator` props.
*/
idSchema: IdSchema<T>;
/** The schemaUtils implementation used by the `Form`, created from the `validator` and the `schema` */
schemaUtils: SchemaUtilsType<T>;
schemaUtils: SchemaUtilsType<T, S, F>;
/** The current data for the form, computed from the `formData` prop and the changes made by the user */
formData: T;
/** Flag indicating whether the form is in edit mode, true when `formData` is passed to the form, otherwise false */
Expand All @@ -214,20 +223,24 @@ export interface FormState<T = any, F = any> {
/** The event data passed when changes have been made to the form, includes everything from the `FormState` except
* the schema validation errors. An additional `status` is added when returned from `onSubmit`
*/
export interface IChangeEvent<T = any, F = any>
extends Omit<
FormState<T, F>,
export interface IChangeEvent<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F = any
> extends Omit<
FormState<T, S, F>,
"schemaValidationErrors" | "schemaValidationErrorSchema"
> {
/** The status of the form when submitted */
status?: "submitted";
}

/** The `Form` component renders the outer form and all the fields defined in the `schema` */
export default class Form<T = any, F = any> extends Component<
FormProps<T, F>,
FormState<T, F>
> {
export default class Form<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F = any
> extends Component<FormProps<T, S, F>, FormState<T, S, F>> {
/** The ref used to hold the `form` element, this needs to be `any` because `tagName` or `_internalFormWrapper` can
* provide any possible type here
*/
Expand All @@ -239,7 +252,7 @@ export default class Form<T = any, F = any> extends Component<
*
* @param props - The initial props for the `Form`
*/
constructor(props: FormProps<T, F>) {
constructor(props: FormProps<T, S, F>) {
super(props);

if (!props.validator) {
Expand All @@ -262,7 +275,7 @@ export default class Form<T = any, F = any> extends Component<
*
* @param nextProps - The new set of props about to be applied to the `Form`
*/
UNSAFE_componentWillReceiveProps(nextProps: FormProps<T, F>) {
UNSAFE_componentWillReceiveProps(nextProps: FormProps<T, S, F>) {
const nextState = this.getStateFromProps(nextProps, nextProps.formData);
if (
!deepEquals(nextState.formData, nextProps.formData) &&
Expand All @@ -283,24 +296,24 @@ export default class Form<T = any, F = any> extends Component<
* @returns - The new state for the `Form`
*/
getStateFromProps(
props: FormProps<T, F>,
props: FormProps<T, S, F>,
inputFormData?: T
): FormState<T, F> {
const state: FormState<T, F> = this.state || {};
): FormState<T, S, F> {
const state: FormState<T, S, F> = this.state || {};
const schema = "schema" in props ? props.schema : this.props.schema;
const uiSchema: UiSchema<T, F> =
const uiSchema: UiSchema<T, S, F> =
("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;
let schemaUtils: SchemaUtilsType<T> = state.schemaUtils;
let schemaUtils: SchemaUtilsType<T, S> = state.schemaUtils;
if (
!schemaUtils ||
schemaUtils.doesSchemaUtilsDiffer(props.validator, rootSchema)
) {
schemaUtils = createSchemaUtils<T>(props.validator, rootSchema);
schemaUtils = createSchemaUtils<T, S, F>(props.validator, rootSchema);
}
const formData: T = schemaUtils.getDefaultFormState(
schema,
Expand Down Expand Up @@ -355,7 +368,7 @@ export default class Form<T = any, F = any> extends Component<
props.idPrefix,
props.idSeparator
);
const nextState: FormState<T, F> = {
const nextState: FormState<T, S, F> = {
schemaUtils,
schema,
uiSchema,
Expand All @@ -377,8 +390,8 @@ export default class Form<T = any, F = any> extends Component<
* @returns - True if the component should be updated, false otherwise
*/
shouldComponentUpdate(
nextProps: FormProps<T, F>,
nextState: FormState<T, F>
nextProps: FormProps<T, S, F>,
nextState: FormState<T, S, F>
): boolean {
return shouldRender(this, nextProps, nextState);
}
Expand All @@ -393,7 +406,7 @@ export default class Form<T = any, F = any> extends Component<
validate(
formData: T,
schema = this.props.schema,
altSchemaUtils?: SchemaUtilsType<T>
altSchemaUtils?: SchemaUtilsType<T, S>
): ValidationData<T> {
const schemaUtils = altSchemaUtils
? altSchemaUtils
Expand All @@ -411,11 +424,11 @@ export default class Form<T = any, F = any> extends Component<
}

/** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */
renderErrors(registry: Registry<T, F>) {
renderErrors(registry: Registry<T, S, F>) {
const { errors, errorSchema, schema, uiSchema } = this.state;
const { showErrorList, formContext } = this.props;
const options = getUiOptions<T, F>(uiSchema);
const ErrorListTemplate = getTemplate<"ErrorListTemplate", T, F>(
const options = getUiOptions<T, S, F>(uiSchema);
const ErrorListTemplate = getTemplate<"ErrorListTemplate", T, S, F>(
"ErrorListTemplate",
registry,
options
Expand Down Expand Up @@ -522,7 +535,7 @@ export default class Form<T = any, F = any> extends Component<
}

const mustValidate = !noValidate && liveValidate;
let state: Partial<FormState<T, F>> = { formData, schema };
let state: Partial<FormState<T, S, F>> = { formData, schema };
let newFormData = formData;

if (omitExtraData === true && liveOmit === true) {
Expand Down Expand Up @@ -573,7 +586,7 @@ export default class Form<T = any, F = any> extends Component<
};
}
this.setState(
state as FormState<T, F>,
state as FormState<T, S, F>,
() => onChange && onChange({ ...this.state, ...state }, id)
);
};
Expand Down Expand Up @@ -664,9 +677,13 @@ export default class Form<T = any, F = any> extends Component<
};

/** Returns the registry for the form */
getRegistry(): Registry<T, F> {
getRegistry(): Registry<T, S, F> {
const { schemaUtils } = this.state;
const { fields, templates, widgets, formContext } = getDefaultRegistry();
const { fields, templates, widgets, formContext } = getDefaultRegistry<
T,
S,
F
>();
return {
fields: { ...fields, ...this.props.fields },
templates: {
Expand Down Expand Up @@ -769,7 +786,7 @@ export default class Form<T = any, F = any> extends Component<
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.
// 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";
Expand Down