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

feat: support error list to show at the top or bottom #3196

Merged
Merged
2 changes: 1 addition & 1 deletion .node-version
@@ -1 +1 @@
16
16.0.0
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -21,6 +21,7 @@ should change the heading of the (upcoming) version to include a major version b
- Automatically close single-choice Select widget on selection

jacqueswho marked this conversation as resolved.
Show resolved Hide resolved
## @rjsf/core
- BREAKING CHANGE: ShowErrorList prop changed to support `false`, `top` or `bottom`; `true` is no longer a valid value as the default changed from `true` to `top` [#634](https://github.com/rjsf-team/react-jsonschema-form/issues/634)
- Added the new generic, `S extends StrictRJSFSchema = RJSFSchema`, for `schema`/`rootSchema` to every component that needed it.

## @rjsf/utils
Expand Down
15 changes: 10 additions & 5 deletions docs/5.x upgrade guide.md
Expand Up @@ -42,6 +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
Expand All @@ -52,7 +57,7 @@ Some of the most notable changes are:
- 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`.
- **BREAKING CHANGE** The `DescriptionField` and `TitleField` props were removed from the `ArrayFieldTemplateProps` and `ObjectFieldTemplateProps` as they can now be derived from the `templates` or `uiSchema` via the new `getTemplate()` utility function.
- **BREAKING CHANGE** The `fields` prop was removed from the `FieldTemplateProps` as you can simply use `registry.fields` instead.

- **BREAKING CHANGE** The `showErrorList` prop was changed to accept `false`, `"top"` or `"bottom"`. `true` is no longer a valid value. The default value is `"top"`, which has identical behavior to the default value/`true` in v4.
You can view all these [types](https://github.com/rjsf-team/react-jsonschema-form/blob/main/packages/utils/src/types.ts) on Github.

#### Form props
Expand Down Expand Up @@ -121,7 +126,7 @@ 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.
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`.
Expand Down Expand Up @@ -252,8 +257,8 @@ function YourWidget(props: WidgetProps) {
const isMultiSelect = schemaUtils.isMultiSelect(schema);
const newSchema: RJSFSchema = schemaUtils.retrieveSchema(schema, formData);
const options = getUiOptions(uiSchema);
...

...
}
```

Expand Down Expand Up @@ -406,7 +411,7 @@ If you are using `classNames` as follows, simply add the `ui:` prefix to it to r
// This uiSchema will log a deprecation warning to the console
const uiSchema = {
title: {
"classNames": "myClass"
"classNames": "myClass"
}
};
// This uiSchema will not
Expand Down
4 changes: 2 additions & 2 deletions docs/api-reference/form-props.md
Expand Up @@ -246,7 +246,7 @@ render((

## showErrorList

When this prop is set to true, a list of errors (or the custom error list defined in the `ErrorList`) will also show. When set to false, only inline input validation errors will be shown. Set to `true` by default. See [Validation](../usage/validation.md) for more information.
When this prop is set to `top` or `bottom`, a list of errors (or the custom error list defined in the `ErrorList`) will also show at the `bottom` or `top` of the form. When set to false, only inline input validation errors will be shown. Set to `top` by default. See [Validation](../usage/validation.md) for more information.

## tagName

Expand Down Expand Up @@ -288,7 +288,7 @@ Form uiSchema. See [uiSchema Reference](uiSchema.md) for more information.

## validator

**Required**! An implementation of the `ValidatorType` interface that is needed for form validation to work.
**Required**! An implementation of the `ValidatorType` interface that is needed for form validation to work.
`@rjsf/validator-ajv6` exports the implementation of this interface from RJSF version 4.

## widgets
Expand Down
12 changes: 6 additions & 6 deletions docs/usage/validation.md
Expand Up @@ -7,7 +7,7 @@ React Json Schema Form provides a default `@rjsf/validator-ajv6` implementation

It also provides a new `@rjsf/validator-ajv8` implementation that uses version 8 of the [ajv](https://github.com/ajv-validator/ajv) validator.
The error messages generated by this new validator differ from those provided by the original validator due to it using a newer version.
If you depend on having specifically formatted messages, then using this validator would constitute a breaking change for you.
If you depend on having specifically formatted messages, then using this validator would constitute a breaking change for you.

It is also possible for you to provide your own implementation if you desire, as long as it fulfills the `ValidatorType` interface specified in `@rjsf/utils`.

Expand Down Expand Up @@ -200,14 +200,14 @@ const schema: RJSFSchema = {
render((
<Form schema={schema}
validator={validator}
showErrorList={true}
showErrorList='top'
formData={""}
liveValidate
templates: {{ ErrorListTemplate }} />
), document.getElementById("app"));
```

> Note: Your custom `ErrorList` template will only render when `showErrorList` is `true`.
> Note: Your custom `ErrorList` template will only render when `showErrorList` is `top` or `botttom`.

The following props are passed to `ErrorList` as defined by the `ErrorListProps` interface in `@rjsf/utils`:

Expand All @@ -220,7 +220,7 @@ The following props are passed to `ErrorList` as defined by the `ErrorListProps`
## The case of empty strings

When a text input is empty, the field in form data is set to `undefined`.
However, since `undefined` isn't a valid JSON value according to [the official JSON standard](https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf) (ECMA-404, Section 5), the values get stored as `null`.
However, since `undefined` isn't a valid JSON value according to [the official JSON standard](https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf) (ECMA-404, Section 5), the values get stored as `null`.

String fields that use `enum` and a `select` widget will have an empty option at the top of the options list that when selected will result in the field being `null`.

Expand Down Expand Up @@ -522,7 +522,7 @@ function localize_ru(errors: null | ErrorObject[] = []) {
if (!(errors && errors.length)) return;
errors.forEach(function(error) {
let outMessage = "";

switch (error.keyword) {
case "pattern": {
outMessage = 'должно соответствовать образцу "' + error.params.pattern + '"';
Expand All @@ -535,7 +535,7 @@ function localize_ru(errors: null | ErrorObject[] = []) {
default:
outMessage = error.message;
}

error.message = outMessage;
})
}
Expand Down
40 changes: 21 additions & 19 deletions packages/core/src/components/Form.tsx
@@ -1,35 +1,35 @@
import React, { Component } from "react";
import {
heath-freenome marked this conversation as resolved.
Show resolved Hide resolved
createSchemaUtils,
CustomValidator,
deepEquals,
ErrorSchema,
ErrorTransformer,
GenericObjectType,
getTemplate,
getUiOptions,
IdSchema,
isObject,
mergeObjects,
NAME_KEY,
PathSchema,
RJSFSchema,
StrictRJSFSchema,
RJSFValidationError,
Registry,
RegistryWidgetsType,
RegistryFieldsType,
RegistryWidgetsType,
RJSFSchema,
RJSFValidationError,
RJSF_ADDITONAL_PROPERTIES_FLAG,
SchemaUtilsType,
shouldRender,
TemplatesType,
UiSchema,
ValidationData,
ValidatorType,
createSchemaUtils,
deepEquals,
getTemplate,
getUiOptions,
isObject,
mergeObjects,
shouldRender,
NAME_KEY,
RJSF_ADDITONAL_PROPERTIES_FLAG,
} from "@rjsf/utils";
import _pick from "lodash/pick";
import _get from "lodash/get";
import _isEmpty from "lodash/isEmpty";
import _pick from "lodash/pick";

import getDefaultRegistry from "../getDefaultRegistry";

Expand Down Expand Up @@ -161,10 +161,10 @@ export interface FormProps<
* called. Set to `false` by default.
*/
omitExtraData?: boolean;
/** When this prop is set to true, a list of errors (or the custom error list defined in the `ErrorList`) will also
* show. When set to false, only inline input validation errors will be shown. Set to `true` by default
/** When this prop is set to `top` or 'bottom', a list of errors (or the custom error list defined in the `ErrorList`) will also
* show. When set to false, only inline input validation errors will be shown. Set to `top` by default
*/
showErrorList?: boolean;
showErrorList?: false | "top" | "bottom";
/** A function can be passed to this prop in order to make modifications to the default errors resulting from JSON
* Schema validation
*/
Expand Down Expand Up @@ -426,15 +426,15 @@ export default class Form<
/** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */
renderErrors(registry: Registry<T, S, F>) {
const { errors, errorSchema, schema, uiSchema } = this.state;
const { showErrorList, formContext } = this.props;
const { formContext } = this.props;
const options = getUiOptions<T, S, F>(uiSchema);
const ErrorListTemplate = getTemplate<"ErrorListTemplate", T, S, F>(
"ErrorListTemplate",
registry,
options
);

if (errors && errors.length && showErrorList != false) {
if (errors && errors.length) {
return (
<ErrorListTemplate
errors={errors}
Expand Down Expand Up @@ -778,6 +778,7 @@ export default class Form<
disabled = false,
readonly = false,
formContext,
showErrorList = "top",
_internalFormWrapper,
} = this.props;

Expand Down Expand Up @@ -807,7 +808,7 @@ export default class Form<
as={as}
ref={this.formElement}
>
{this.renderErrors(registry)}
{showErrorList === "top" && this.renderErrors(registry)}
<_SchemaField
name=""
schema={schema}
Expand All @@ -826,6 +827,7 @@ export default class Form<
readonly={readonly}
/>
{children ? children : <SubmitButton uiSchema={uiSchema} />}
{showErrorList === "bottom" && this.renderErrors(registry)}
</FormTag>
);
}
Expand Down
68 changes: 68 additions & 0 deletions packages/core/test/validate_test.js
Expand Up @@ -22,6 +22,71 @@ describe("Validation", () => {
});

describe("JSONSchema validation", () => {
describe("ShowErrorList prop top", () => {
const schema = {
type: "object",
required: ["foo"],
properties: {
foo: { type: "string" },
bar: { type: "string" },
},
};

let node;
const compInfo = createFormComponent({
schema,
formData: {
foo: undefined,
},
});
node = compInfo.node;
submitForm(node);

it("should render errors at the top", () => {
expect(node.querySelectorAll(".errors li")).to.have.length.of(1);
expect(node.querySelector(".errors li").textContent).eql(
".foo is a required property"
);
expect(node.childNodes[0].className).to.eql(
"panel panel-danger errors"
);
});
});

describe("ShowErrorList prop bottom", () => {
const schema = {
type: "object",
required: ["foo"],
properties: {
foo: { type: "string" },
bar: { type: "string" },
},
};

let node;
const compInfo = createFormComponent({
showErrorList: "bottom",
schema,
formData: {
foo: undefined,
},
});
node = compInfo.node;
submitForm(node);

it("should render errors at the bottom", () => {
expect(node.querySelectorAll(".errors li")).to.have.length.of(1);
expect(node.querySelector(".errors li").textContent).eql(
".foo is a required property"
);

// The last child node is the submit button so the one before it will be the error list
expect(node.childNodes[2].className).to.eql(
"panel panel-danger errors"
);
});
});

describe("Required fields", () => {
const schema = {
type: "object",
Expand Down Expand Up @@ -63,6 +128,9 @@ describe("Validation", () => {
expect(node.querySelector(".errors li").textContent).eql(
".foo is a required property"
);
expect(node.childNodes[0].className).to.eql(
"panel panel-danger errors"
);
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/playground/src/app.jsx
Expand Up @@ -21,6 +21,7 @@ const liveSettingsSchema = {
omitExtraData: { type: "boolean", title: "Omit extra data" },
liveOmit: { type: "boolean", title: "Live omit" },
noValidate: { type: "boolean", title: "Disable validation" },
showErrorList:{ type: "string", "default":"top", title: "Show Error List", enum:[false,"top","bottom"] }
},
};

Expand Down