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] Fix custom validation playground example #3858

Merged
merged 4 commits into from
Oct 5, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ return (<StyleProvider><YourFormComponents /></StyleProvider>);

- Fixed a faulty check of the `isMultiple` option in `MultiSchemaField`. It no longer offers multiple choice inside a select field in a `oneOf` case in Chakra UI, fixing [#3848](https://github.com/rjsf-team/react-jsonschema-form/issues/3848)

## Dev / docs / playground

- Fixed custom validation playground example ([#3856](https://github.com/rjsf-team/react-jsonschema-form/issues/3856))

# 5.12.1

## @rjsf/validator-ajv8
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 16 additions & 25 deletions packages/docs/docs/usage/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ const validator = createPrecompiledValidator(precompiledValidator as ValidatorFu

render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
```

### Dynamically pre-compiling validators

For more advanced cases when schema needs to be precompiled on request - `compileSchemaValidatorsCode` can be used.

```ts
import { compileSchemaValidatorsCode } from '@rjsf/validator-ajv8/dist/compileSchemaValidators';

Expand All @@ -103,7 +105,7 @@ For the most part it is the same as `compileSchemaValidators`, but instead of wr

To use it on browser side - some modifications are needed to provide runtime dependencies in generated code needs to be provided.

Example implementation of it:
Example implementation of it:

```tsx
import type { ValidatorFunctions } from '@rjsf/validator-ajv8';
Expand All @@ -121,19 +123,13 @@ import ajvRuntimeUcs2length from 'ajv/dist/runtime/ucs2length';
import ajvRuntimeUri from 'ajv/dist/runtime/uri';
import * as ajvFormats from 'ajv-formats/dist/formats';

// dependencies to replace in generated code, to be provided by at runtime
// dependencies to replace in generated code, to be provided by at runtime
const validatorsBundleReplacements: Record<string, [string, unknown]> = {
// '<code to be replaced>': ['<variable name to use as replacement>', <runtime dependency>],
// '<code to be replaced>': ['<variable name to use as replacement>', <runtime dependency>],
'require("ajv/dist/runtime/equal").default': ['ajvRuntimeEqual', ajvRuntimeEqual],
'require("ajv/dist/runtime/parseJson").parseJson': ['ajvRuntimeparseJson', ajvRuntimeparseJson],
'require("ajv/dist/runtime/parseJson").parseJsonNumber': [
'ajvRuntimeparseJsonNumber',
ajvRuntimeparseJsonNumber,
],
'require("ajv/dist/runtime/parseJson").parseJsonString': [
'ajvRuntimeparseJsonString',
ajvRuntimeparseJsonString,
],
'require("ajv/dist/runtime/parseJson").parseJsonNumber': ['ajvRuntimeparseJsonNumber', ajvRuntimeparseJsonNumber],
'require("ajv/dist/runtime/parseJson").parseJsonString': ['ajvRuntimeparseJsonString', ajvRuntimeparseJsonString],
'require("ajv/dist/runtime/quote").default': ['ajvRuntimeQuote', ajvRuntimeQuote],
// re2 by default is not in dependencies for ajv and so is likely not normally used
// 'require("ajv/dist/runtime/re2").default': ['ajvRuntimeRe2', ajvRuntimeRe2],
Expand Down Expand Up @@ -167,10 +163,7 @@ const schemas = new Map<
>();
if (typeof window !== 'undefined') {
// @ts-ignore
window[windowValidatorOnLoad] = (
loadedId: string,
fn: (...args: unknown[]) => ValidatorFunctions
) => {
window[windowValidatorOnLoad] = (loadedId: string, fn: (...args: unknown[]) => ValidatorFunctions) => {
const validator = fn(...Object.values(validatorsBundleReplacements).map(([, dep]) => dep));
let validatorLoader = schemas.get(loadedId);
if (validatorLoader) {
Expand Down Expand Up @@ -207,28 +200,26 @@ export function evaluateValidator(id: string, code: string, nonce: string): Prom
document.body.appendChild(scriptElement);
return validatorPromise;
}

```

From React component this can be used as following:
From React component this can be used as following:

```tsx
let [precompiledValidator, setPrecompiledValidator] = React.useState<ValidatorFunctions>();
React.useEffect(() => {
evaluateValidator(
schemaId, // some schema id to avoid evaluating it multiple times
code, // result of compileSchemaValidatorsCode returned from the server
nonce // nonce script tag attribute to allow this ib content security policy for the page
).then(setPrecompiledValidator);
evaluateValidator(
schemaId, // some schema id to avoid evaluating it multiple times
code, // result of compileSchemaValidatorsCode returned from the server
nonce // nonce script tag attribute to allow this ib content security policy for the page
).then(setPrecompiledValidator);
}, [entityType.id]);

if (!precompiledValidator) {
// render loading screen
// render loading screen
}
const validator = createPrecompiledValidator(precompiledValidator, schema);
```


## Live validation

By default, form data are only validated when the form is submitted or when a new `formData` prop is passed to the `Form` component.
Expand Down Expand Up @@ -526,7 +517,7 @@ const validator = customizeValidator({ customFormats });
render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
```

Format values can be anything AJVs [`addFormat` method](https://github.com/ajv-validator/ajv/tree/6a671057ea6aae690b5967ee26a0ddf8452c6297#addformatstring-name-stringregexpfunctionobject-format---ajv) accepts.
Format values can be anything AJV's [`addFormat` method](https://github.com/ajv-validator/ajv/tree/6a671057ea6aae690b5967ee26a0ddf8452c6297#addformatstring-name-stringregexpfunctionobject-format---ajv) accepts.

### Async validation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ render((
), document.getElementById("app"));
```

Format values can be anything AJVs [`addFormat` method](https://github.com/ajv-validator/ajv/tree/6a671057ea6aae690b5967ee26a0ddf8452c6297#addformatstring-name-stringregexpfunctionobject-format---ajv) accepts.
Format values can be anything AJV's [`addFormat` method](https://github.com/ajv-validator/ajv/tree/6a671057ea6aae690b5967ee26a0ddf8452c6297#addformatstring-name-stringregexpfunctionobject-format---ajv) accepts.

## Async validation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ render((
), document.getElementById("app"));
```

Format values can be anything AJVs [`addFormat` method](https://github.com/ajv-validator/ajv/tree/6a671057ea6aae690b5967ee26a0ddf8452c6297#addformatstring-name-stringregexpfunctionobject-format---ajv) accepts.
Format values can be anything AJV's [`addFormat` method](https://github.com/ajv-validator/ajv/tree/6a671057ea6aae690b5967ee26a0ddf8452c6297#addformatstring-name-stringregexpfunctionobject-format---ajv) accepts.

## Async validation

Expand Down
1 change: 1 addition & 0 deletions packages/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"chakra-react-select": "^4.7.0",
"core-js": "^3.32.2",
"dayjs": "^1.11.9",
"deep-freeze-es6": "^1.4.1",
"framer-motion": "^5.6.0",
"jss": "^10.10.0",
"lodash": "^4.17.21",
Expand Down
34 changes: 22 additions & 12 deletions packages/playground/src/components/Playground.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useState, useRef, useEffect, ComponentType, FormEvent } from 'react';
import { withTheme, IChangeEvent, FormProps } from '@rjsf/core';
import { ErrorSchema, RJSFSchema, RJSFValidationError, TemplatesType, UiSchema, ValidatorType } from '@rjsf/utils';
import { ComponentType, FormEvent, useCallback, useEffect, useRef, useState } from 'react';
import { FormProps, IChangeEvent, withTheme } from '@rjsf/core';
import { ErrorSchema, RJSFSchema, RJSFValidationError, UiSchema, ValidatorType } from '@rjsf/utils';

import { samples } from '../samples';
import Header, { LiveSettings } from './Header';
Expand All @@ -10,6 +10,7 @@ import GeoPosition from './GeoPosition';
import { ThemesType } from './ThemeSelector';
import Editors from './Editors';
import SpecialInput from './SpecialInput';
import { Sample } from '../samples/Sample';

export interface PlaygroundProps {
themes: { [themeName: string]: ThemesType };
Expand Down Expand Up @@ -39,7 +40,7 @@ export default function Playground({ themes, validators }: PlaygroundProps) {
experimental_defaultFormStateBehavior: { arrayMinItems: 'populate', emptyObjectFields: 'populateAllDefaults' },
});
const [FormComponent, setFormComponent] = useState<ComponentType<FormProps>>(withTheme({}));
const [templates, setTemplates] = useState<Partial<TemplatesType>>();
const [otherFormProps, setOtherFormProps] = useState<Partial<FormProps>>({});
Copy link
Contributor Author

@nickgros nickgros Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capture all of the form props that only change on load in this object. templates can be included, so they don't need their own state variable.


const playGroundFormRef = useRef<any>(null);

Expand All @@ -54,12 +55,21 @@ export default function Playground({ themes, validators }: PlaygroundProps) {
);

const load = useCallback(
(data: any) => {
// Reset the ArrayFieldTemplate whenever you load new data
const { templates = {}, extraErrors, liveSettings } = data;
// uiSchema is missing on some examples. Provide a default to
// clear the field in all cases.
const { schema, uiSchema = {}, formData, theme: dataTheme = theme } = data;
(data: Sample & { theme: string; liveSettings: LiveSettings }) => {
const {
schema,
// uiSchema is missing on some examples. Provide a default to
// clear the field in all cases.
uiSchema = {},
// Always reset templates and fields
templates = {},
fields = {},
formData,
theme: dataTheme = theme,
extraErrors,
liveSettings,
...rest
} = data;

onThemeSelected(dataTheme, themes[dataTheme]);

Expand All @@ -70,9 +80,9 @@ export default function Playground({ themes, validators }: PlaygroundProps) {
setFormData(formData);
setExtraErrors(extraErrors);
setTheme(dataTheme);
setTemplates(templates);
setShowForm(true);
setLiveSettings(liveSettings);
setOtherFormProps({ fields, templates, ...rest });
},
[theme, onThemeSelected, themes]
);
Expand Down Expand Up @@ -166,8 +176,8 @@ export default function Playground({ themes, validators }: PlaygroundProps) {
theme={theme}
>
<FormComponent
{...otherFormProps}
{...liveSettings}
templates={templates}
extraErrors={extraErrors}
schema={schema}
uiSchema={uiSchema}
Expand Down
3 changes: 3 additions & 0 deletions packages/playground/src/samples/Sample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { FormProps } from '@rjsf/core';

export type Sample = Omit<FormProps, 'validator'>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main cause for the playground example breaking was that it didn't get updated when the API changed in v5. This is more easily caught if the samples are all typed.

6 changes: 5 additions & 1 deletion packages/playground/src/samples/additionalProperties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import { Sample } from './Sample';

const additionalProperties: Sample = {
schema: {
title: 'A customizable registration form',
description: 'A simple form with additional properties example.',
Expand Down Expand Up @@ -30,3 +32,5 @@ export default {
assKickCount: 'infinity',
},
};

export default additionalProperties;
6 changes: 5 additions & 1 deletion packages/playground/src/samples/allOf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import { Sample } from './Sample';

const allOf: Sample = {
schema: {
type: 'object',
allOf: [
Expand All @@ -24,3 +26,5 @@ export default {
},
formData: {},
};

export default allOf;
6 changes: 5 additions & 1 deletion packages/playground/src/samples/alternatives.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import { Sample } from './Sample';

const alternatives: Sample = {
schema: {
definitions: {
Color: {
Expand Down Expand Up @@ -90,3 +92,5 @@ export default {
blendMode: 'screen',
},
};

export default alternatives;
6 changes: 5 additions & 1 deletion packages/playground/src/samples/anyOf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import { Sample } from './Sample';

const anyOf: Sample = {
schema: {
type: 'object',
properties: {
Expand Down Expand Up @@ -57,3 +59,5 @@ export default {
},
formData: {},
};

export default anyOf;
6 changes: 5 additions & 1 deletion packages/playground/src/samples/arrays.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import { Sample } from './Sample';

const arrays: Sample = {
schema: {
definitions: {
Thing: {
Expand Down Expand Up @@ -189,3 +191,5 @@ export default {
fixedNoToolbar: [42, true, 'additional item one', 'additional item two'],
},
};

export default arrays;
6 changes: 5 additions & 1 deletion packages/playground/src/samples/custom.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import { Sample } from './Sample';

const custom: Sample = {
schema: {
title: 'A localisation form',
type: 'object',
Expand All @@ -20,3 +22,5 @@ export default {
lon: 0,
},
};

export default custom;
17 changes: 9 additions & 8 deletions packages/playground/src/samples/customArray.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
const ArrayFieldTemplate: React.FC<{ className: string; items?: any[]; canAdd?: boolean; onAddClick: () => void }> = ({
className,
items,
canAdd,
onAddClick,
}) => {
import { Sample } from './Sample';
import { ArrayFieldTemplateProps } from '@rjsf/utils';

function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
const { className, items, canAdd, onAddClick } = props;
return (
<div className={className}>
{items &&
Expand Down Expand Up @@ -32,9 +31,9 @@ const ArrayFieldTemplate: React.FC<{ className: string; items?: any[]; canAdd?:
)}
</div>
);
};
}

export default {
export const customArray: Sample = {
schema: {
title: 'Custom array of strings',
type: 'array',
Expand All @@ -45,3 +44,5 @@ export default {
formData: ['react', 'jsonschema', 'form'],
templates: { ArrayFieldTemplate },
};

export default customArray;
6 changes: 5 additions & 1 deletion packages/playground/src/samples/customField.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import { Sample } from './Sample';

const customField: Sample = {
schema: {
title: 'A registration form',
description: 'A custom-field form example.',
Expand Down Expand Up @@ -26,3 +28,5 @@ export default {
mySpecialStringField: 'special-text',
},
};

export default customField;
Loading