Skip to content

Commit

Permalink
feat: add custom component by id (#3740)
Browse files Browse the repository at this point in the history
Co-authored-by: Nick Grosenbacher <[email protected]>
  • Loading branch information
lironhl and nickgros authored Jul 3, 2023
1 parent 5bb9601 commit 0d30be8
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 2 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/components/fields/SchemaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ function getFieldComponent<T = any, S extends StrictRJSFSchema = RJSFSchema, F e

const schemaType = getSchemaType(schema);
const type: string = Array.isArray(schemaType) ? schemaType[0] : schemaType || '';
const componentName = COMPONENT_TYPES[type];

const schemaId = schema.$id;

let componentName = COMPONENT_TYPES[type];
if (schemaId && schemaId in fields) {
componentName = schemaId;
}

// If the type is not defined and the schema uses 'anyOf' or 'oneOf', don't
// render a field and let the MultiSchemaField component handle the form display
Expand Down
35 changes: 35 additions & 0 deletions packages/core/test/SchemaField.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,41 @@ describe('SchemaField', () => {
});
});

describe('Custom type component', () => {
const CustomStringField = function () {
return <div id='custom-type' />;
};

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 <div id='custom-id' />;
};

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) {
Expand Down
33 changes: 33 additions & 0 deletions packages/docs/docs/advanced-customization/custom-widgets-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,36 @@ const schema: RJSFSchema = {

render(<Form schema={schema} validator={validator} fields={fields} />, 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 (
<div id='custom'>
<p>Yeah, I'm pretty dumb.</p>
<div>My props are: {JSON.stringify(props)}</div>
</div>
);
};

const fields: RegistryFieldsType = {
'/schemas/my-id': CustomIdField,
};

const schema: RJSFSchema = {
$id: '/schemas/my-id',
type: 'string',
};

render(<Form schema={schema} validator={validator} fields={fields} />, document.getElementById('app'));
```
6 changes: 5 additions & 1 deletion packages/playground/src/components/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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}
Expand Down
37 changes: 37 additions & 0 deletions packages/playground/src/components/SpecialInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FieldProps } from '@rjsf/utils';
import { FC, useState } from 'react';

const COLORS = ['red', 'green', 'blue'];

const SpecialInput: FC<FieldProps<string>> = ({ onChange, formData }) => {
const [text, setText] = useState<string>(formData || '');

const inputBgColor = COLORS[text.length % COLORS.length];

return (
<div className='SpecialInput'>
<h3>Hey, I&apos;m a custom component</h3>
<p>
I&apos;m registered as <code>/schemas/specialString</code> and referenced in
<code>Form</code>&apos;s <code>field</code> prop to use for this schema anywhere this schema <code>$id</code> is
used.
</p>
<div className='row'>
<div className='col-sm-6'>
<label>SpecialInput</label>
<input
className='form-control'
style={{ background: inputBgColor, color: 'white', fontSize: 14 }}
value={text}
onChange={({ target: { value } }) => {
onChange(value);
setText(value);
}}
/>
</div>
</div>
</div>
);
};

export default SpecialInput;
28 changes: 28 additions & 0 deletions packages/playground/src/samples/customField.ts
Original file line number Diff line number Diff line change
@@ -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',
},
};
2 changes: 2 additions & 0 deletions packages/playground/src/samples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {} },
Expand Down Expand Up @@ -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;

0 comments on commit 0d30be8

Please sign in to comment.