From 0d30be8bdc14a1d9b706235088c377ccf0994c5d Mon Sep 17 00:00:00 2001 From: Liron Haim Lavy Date: Tue, 4 Jul 2023 00:29:57 +0300 Subject: [PATCH] feat: add custom component by id (#3740) Co-authored-by: Nick Grosenbacher --- CHANGELOG.md | 12 ++++++ .../src/components/fields/SchemaField.tsx | 8 +++- packages/core/test/SchemaField.test.jsx | 35 ++++++++++++++++++ .../custom-widgets-fields.md | 33 +++++++++++++++++ .../playground/src/components/Playground.tsx | 6 ++- .../src/components/SpecialInput.tsx | 37 +++++++++++++++++++ .../playground/src/samples/customField.ts | 28 ++++++++++++++ packages/playground/src/samples/index.ts | 2 + 8 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 packages/playground/src/components/SpecialInput.tsx create mode 100644 packages/playground/src/samples/customField.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e572ad47..dd4e4241fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,18 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> +# 5.10.0 + +## @rjsf/core + +- Updated `getFieldComponent()` to support rendering a custom component by given schema id ($id). [#3740](https://github.com/rjsf-team/react-jsonschema-form/pull/3740) + +## Dev / docs / playground + +- Updated the `custom-widgets-fields` documentation to add the new added behaviour of `getFieldComponent()` function. [#3740](https://github.com/rjsf-team/react-jsonschema-form/pull/3740) +- Updated the `playground` to add an example of the new added behaviour of `getFieldComponent()` function. [#3740](https://github.com/rjsf-team/react-jsonschema-form/pull/3740) + + # 5.9.0 ## @rjsf/utils diff --git a/packages/core/src/components/fields/SchemaField.tsx b/packages/core/src/components/fields/SchemaField.tsx index 359ce3cc51..24cfcf7c81 100644 --- a/packages/core/src/components/fields/SchemaField.tsx +++ b/packages/core/src/components/fields/SchemaField.tsx @@ -62,7 +62,13 @@ function getFieldComponent { }); }); + describe('Custom type component', () => { + const CustomStringField = function () { + return
; + }; + + it('should use custom type component', () => { + const fields = { StringField: CustomStringField }; + const { node } = createFormComponent({ + schema: { type: 'string' }, + fields, + }); + + expect(node.querySelectorAll('#custom-type')).to.have.length.of(1); + }); + }); + + describe('Custom id component', () => { + const CustomIdField = function () { + return
; + }; + + it('should use custom id compnent', () => { + const fields = { '/schemas/custom-id': CustomIdField }; + const { node } = createFormComponent({ + schema: { + $id: '/schemas/custom-id', + type: 'string', + }, + fields, + }); + + expect(node.querySelectorAll('#custom-id')).to.have.length.of(1); + }); + }); + describe('ui:field support', () => { class MyObject extends Component { constructor(props) { diff --git a/packages/docs/docs/advanced-customization/custom-widgets-fields.md b/packages/docs/docs/advanced-customization/custom-widgets-fields.md index 854ab2779d..6c1f4fbda7 100644 --- a/packages/docs/docs/advanced-customization/custom-widgets-fields.md +++ b/packages/docs/docs/advanced-customization/custom-widgets-fields.md @@ -398,3 +398,36 @@ const schema: RJSFSchema = { render(
, document.getElementById('app')); ``` + +### Custom Field by Id + +**Warning:** This is a powerful feature as you can override the whole form behavior and easily mess it up. Handle with care. + +You can provide your own implementation of the field component that applies to any schema or sub-schema based on the schema's `$id` value. This is useful when your custom field should be conditionally applied based on the schema rather than the property name or data type. + +To provide a custom field in this way, the `fields` prop should be an object which contains a key that matches the `$id` value of the schema which should have a custom field; here's an example: + +```tsx +import { RJSFSchema, FieldProps, RegistryFieldsType } from '@rjsf/utils'; +import validator from '@rjsf/validator-ajv8'; + +const CustomIdField = function (props: FieldProps) { + return ( +
+

Yeah, I'm pretty dumb.

+
My props are: {JSON.stringify(props)}
+
+ ); +}; + +const fields: RegistryFieldsType = { + '/schemas/my-id': CustomIdField, +}; + +const schema: RJSFSchema = { + $id: '/schemas/my-id', + type: 'string', +}; + +render(, document.getElementById('app')); +``` diff --git a/packages/playground/src/components/Playground.tsx b/packages/playground/src/components/Playground.tsx index 6fc8c53fa6..bccf32a0df 100644 --- a/packages/playground/src/components/Playground.tsx +++ b/packages/playground/src/components/Playground.tsx @@ -9,6 +9,7 @@ import ErrorBoundary from './ErrorBoundary'; import GeoPosition from './GeoPosition'; import { ThemesType } from './ThemeSelector'; import Editors from './Editors'; +import SpecialInput from './SpecialInput'; export interface PlaygroundProps { themes: { [themeName: string]: ThemesType }; @@ -180,7 +181,10 @@ export default function Playground({ themes, validators }: PlaygroundProps) { schema={schema} uiSchema={uiSchema} formData={formData} - fields={{ geo: GeoPosition }} + fields={{ + geo: GeoPosition, + '/schemas/specialString': SpecialInput, + }} validator={validators[validator]} onChange={onFormDataChange} onSubmit={onFormDataSubmit} diff --git a/packages/playground/src/components/SpecialInput.tsx b/packages/playground/src/components/SpecialInput.tsx new file mode 100644 index 0000000000..9e47ccb0dd --- /dev/null +++ b/packages/playground/src/components/SpecialInput.tsx @@ -0,0 +1,37 @@ +import { FieldProps } from '@rjsf/utils'; +import { FC, useState } from 'react'; + +const COLORS = ['red', 'green', 'blue']; + +const SpecialInput: FC> = ({ onChange, formData }) => { + const [text, setText] = useState(formData || ''); + + const inputBgColor = COLORS[text.length % COLORS.length]; + + return ( +
+

Hey, I'm a custom component

+

+ I'm registered as /schemas/specialString and referenced in + Form's field prop to use for this schema anywhere this schema $id is + used. +

+
+
+ + { + onChange(value); + setText(value); + }} + /> +
+
+
+ ); +}; + +export default SpecialInput; diff --git a/packages/playground/src/samples/customField.ts b/packages/playground/src/samples/customField.ts new file mode 100644 index 0000000000..40c07fb9b2 --- /dev/null +++ b/packages/playground/src/samples/customField.ts @@ -0,0 +1,28 @@ +export default { + schema: { + title: 'A registration form', + description: 'A custom-field form example.', + type: 'object', + definitions: { + specialString: { + $id: '/schemas/specialString', + type: 'string', + }, + }, + properties: { + mySpecialStringField: { + $ref: '#/definitions/specialString', + }, + mySpecialStringArray: { + type: 'array', + items: { + $ref: '#/definitions/specialString', + }, + }, + }, + }, + uiSchema: {}, + formData: { + mySpecialStringField: 'special-text', + }, +}; diff --git a/packages/playground/src/samples/index.ts b/packages/playground/src/samples/index.ts index b0642bb674..3e868997af 100644 --- a/packages/playground/src/samples/index.ts +++ b/packages/playground/src/samples/index.ts @@ -30,6 +30,7 @@ import errorSchema from './errorSchema'; import defaults from './defaults'; import options from './options'; import ifThenElse from './ifThenElse'; +import customField from './customField'; export const samples = Object.freeze({ Blank: { schema: {}, uiSchema: {}, formData: {} }, @@ -65,6 +66,7 @@ export const samples = Object.freeze({ Nullable: nullable, ErrorSchema: errorSchema, Defaults: defaults, + 'Custom Field': customField, } as const); export type Sample = keyof typeof samples;