Skip to content

Commit

Permalink
fix: Partially fix #3189 (#3200)
Browse files Browse the repository at this point in the history
* fix: Partially fix #3189
- Updated `@rjsf/utils` to make the `schema` and `rootSchema` props use a new generic type `S`
  - Added the new `StrictRJSFSchema` type as the alias to `JSON7Schema` changing `RJSFSchema` to be `StrictRJSFSchema & GenericObjectType`
  - Deleted the `RJSFSchemaDefinition` type in favor of accessing it indirectly via the `S["<prop-with-definition>"]` syntax
  - Added the new generic `S extends StrictRJSFSchema = RJSFSchema` to all types that directly or indirectly used `RJSFSchema` after the `T = any` type
  - Updated `SchemaUtilsType` to add the `F = any` generic to the whole interface, removing it from the definition of the two functions that need it
  - Updated all functions that used `RJSFSchema` to take the new generic, replacing `RJSFSchema` with `S`
  - Added missing generics where needed
- Updated `@rjsf/core` to insert the `S extends StrictRJSFSchema = RJSFSchema` to every component that needed it, after the `T = any` generic
  - Updated the `index.ts` for the `ButtonTemplates`, `field`, `templates` and `widgets` to make them functions that take the `T`, `S` and `F` generics
  - Updated `getDefaultRegistry()` and `templates()` to call the appropriate functions
  - Replaced all uses of `RJSFSchema` and `RJSFSchemaDefinition` with `S` and `S["<prop-with-definition>"]`
  - Added missing generics where needed
  - Fixed a few type casts due the to the change in the `RJSFSchema` type
- Updated `@rjsf/validator-ajv6` to fix a few type casts due to the change in the `RJSFSchema` type
- Updated `@rjsf/validator-ajv8` to add the `S extends StrictRJSFSchema = RJSFSchema` generic to the `customizeValidator()` function and the `AJV8Validator` class
  - Replaced all uses of `RJSFSchema` and `RJSFSchemaDefinition` with `S` and `S["<prop-with-definition>"]`
  - Changed `RJSFSchema` to `S` where applicable
  - Fixed a few type casts due the to the change in the `RJSFSchema` type

* Update packages/validator-ajv8/src/validator.ts

Fix comment

Co-authored-by: Nick Grosenbacher <nickgrosenbacher@gmail.com>

Co-authored-by: Nick Grosenbacher <nickgrosenbacher@gmail.com>
  • Loading branch information
heath-freenome and nickgros committed Oct 18, 2022
1 parent 27a8c70 commit b63ec99
Show file tree
Hide file tree
Showing 91 changed files with 1,488 additions and 897 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
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
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
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

0 comments on commit b63ec99

Please sign in to comment.