Skip to content

Commit

Permalink
feat: support error list to show at the top or bottom (#3196)
Browse files Browse the repository at this point in the history
* feat: support error list to show at the top or bottom

Co-authored-by: Heath C <51679588+heath-freenome@users.noreply.github.com>
  • Loading branch information
jacqueswho and heath-freenome committed Oct 26, 2022
1 parent 1a9e719 commit 93c4971
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 33 deletions.
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

## @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 {
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

0 comments on commit 93c4971

Please sign in to comment.