Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ should change the heading of the (upcoming) version to include a major version b
- Fixed issue with default value not being prefilled when object with if/then is nested inside another object, fixing [#4222](https://github.com/rjsf-team/react-jsonschema-form/issues/4222)
- Fixed issue with schema array with nested dependent fixed-length, fixing [#3754](https://github.com/rjsf-team/react-jsonschema-form/issues/3754)
- Updated `CustomValidator` type to accept `errorSchema`, so its implementation can be based on result of ajv validation ([#4898](https://github.com/rjsf-team/react-jsonschema-form/pull/4899))
- Updated `getWidget.tsx` to replace `react-is.isForwardRef()` with `ReactIs.isValidElementType()` for React 19 compatibility, fixing [#4907](https://github.com/rjsf-team/react-jsonschema-form/issues/4907)

## @rjsf/validator-ajv8

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,21 +250,21 @@ This is useful if you expose the `uiSchema` as pure JSON, which can't carry func

### Custom widget options

If you need to pass options to your custom widget, you can add a `ui:options` object containing those properties. If the widget has `defaultProps`, the options will be merged with the (optional) options object from `defaultProps`:
If you need to pass options to your custom widget, you can add a `ui:options` object containing those properties. If the widget has `defaultProps`, the options will be merged with the optional `options` object from `defaultProps`:

```tsx
import { RJSFSchema, UiSchema, WidgetProps } from '@rjsf/utils';
import { RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';

const schema: RJSFSchema = {
type: 'string',
};

function MyCustomWidget(props: WidgetProps) {
const MyCustomWidget = (props: WidgetProps) => {
const { options } = props;
const { color, backgroundColor } = options;
return <input style={{ color, backgroundColor }} />;
}
};

MyCustomWidget.defaultProps = {
options: {
Expand All @@ -283,6 +283,7 @@ const uiSchema: UiSchema = {
render(<Form schema={schema} uiSchema={uiSchema} validator={validator} />, document.getElementById('app'));
```


> Note: This also applies to [registered custom components](#custom-component-registration).

### Customizing widgets' text input
Expand Down
22 changes: 22 additions & 0 deletions packages/docs/docs/migration-guides/v7.x upgrade guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 7.x Upgrade Guide

## Breaking changes

### react-is.isForwardRef() removed

The usage of `react-is.isForwardRef()` was removed from `getWidget.tsx` because React 19 no longer supports this function. ForwardRef components are now detected using `ReactIs.isValidElementType()`, which provides a robust check for all valid React component types.

This change should be transparent to most users, as the widget detection logic now properly handles:

- Regular function components
- Memoized components (`React.memo`)
- ForwardRef components (`React.forwardRef`)
- Other exotic React components

## New deprecations

None in this release.

## Removed deprecations

None in this release.
9 changes: 3 additions & 6 deletions packages/utils/src/getWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { createElement } from 'react';
import ReactIs from 'react-is';
import get from 'lodash/get';
import set from 'lodash/set';
Expand Down Expand Up @@ -101,11 +100,9 @@ export default function getWidget<T = any, S extends StrictRJSFSchema = RJSFSche
): Widget<T, S, F> {
const type = getSchemaType(schema);

if (
typeof widget === 'function' ||
(widget && ReactIs.isForwardRef(createElement(widget))) ||
ReactIs.isMemo(widget)
) {
// Check if widget is a valid React component.
// We exclude strings because in RJSF, string widgets are keys in the registry, not HTML elements.
if (ReactIs.isValidElementType(widget) && typeof widget !== 'string') {
return mergeWidgetOptions<T, S, F>(widget as Widget<T, S, F>);
}
Comment on lines +103 to 107
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually, once we drop defaultProps support in v7 this whole conditional and the mergeWidgetOptions() function will no longer be necessary. Which means that the MergedWidget feature will just go away.


Expand Down
69 changes: 35 additions & 34 deletions packages/utils/src/types.ts
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did you actually change anything in this file or is it just whitespace. If just whitespace, please revert

Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,15 @@ export type FieldPath = {
/** Type describing a recursive structure of `FieldPath`s for an object with a non-empty set of keys */
export type PathSchema<T = any> =
T extends Array<infer U>
? FieldPath & {
[i: number]: PathSchema<U>;
}
: T extends GenericObjectType
? FieldPath & {
/** The set of names for fields in the recursive object structure */
[key in keyof T]?: PathSchema<T[key]>;
}
: FieldPath;
? FieldPath & {
[i: number]: PathSchema<U>;
}
: T extends GenericObjectType
? FieldPath & {
/** The set of names for fields in the recursive object structure */
[key in keyof T]?: PathSchema<T[key]>;
}
: FieldPath;

/** The type for error produced by RJSF schema validation */
export type RJSFValidationError = {
Expand Down Expand Up @@ -480,8 +480,8 @@ export interface Registry<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
/** The properties that are passed to a `Field` implementation */
export interface FieldProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>
extends GenericObjectType,
RJSFBaseProps<T, S, F>,
Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus' | 'onChange'>> {
RJSFBaseProps<T, S, F>,
Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus' | 'onChange'>> {
/** The FieldPathId of the field in the hierarchy */
fieldPathId: FieldPathId;
/** The data for this field */
Expand Down Expand Up @@ -857,25 +857,25 @@ export type WrapIfAdditionalTemplateProps<
/** The field or widget component instance for this field row */
children: ReactNode;
} & Pick<
FieldTemplateProps<T, S, F>,
| 'id'
| 'classNames'
| 'hideError'
| 'rawDescription'
| 'rawErrors'
| 'style'
| 'displayLabel'
| 'label'
| 'required'
| 'readonly'
| 'disabled'
| 'schema'
| 'uiSchema'
| 'onKeyRename'
| 'onKeyRenameBlur'
| 'onRemoveProperty'
| 'registry'
>;
FieldTemplateProps<T, S, F>,
| 'id'
| 'classNames'
| 'hideError'
| 'rawDescription'
| 'rawErrors'
| 'style'
| 'displayLabel'
| 'label'
| 'required'
| 'readonly'
| 'disabled'
| 'schema'
| 'uiSchema'
| 'onKeyRename'
| 'onKeyRenameBlur'
| 'onRemoveProperty'
| 'registry'
>;

/** The properties that are passed to a MultiSchemaFieldTemplate implementation */
export interface MultiSchemaFieldTemplateProps<
Expand All @@ -892,8 +892,8 @@ export interface MultiSchemaFieldTemplateProps<
/** The properties that are passed to a `Widget` implementation */
export interface WidgetProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>
extends GenericObjectType,
RJSFBaseProps<T, S, F>,
Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus' | 'onChange'>> {
RJSFBaseProps<T, S, F>,
Pick<HTMLAttributes<HTMLElement>, Exclude<keyof HTMLAttributes<HTMLElement>, 'onBlur' | 'onFocus' | 'onChange'>> {
/** The generated id for this widget, used to provide unique `name`s and `id`s for the HTML field elements rendered by
* widgets
*/
Expand Down Expand Up @@ -948,6 +948,7 @@ export type Widget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends F
WidgetProps<T, S, F>
>;


/** The properties that are passed to the BaseInputTemplate */
export interface BaseInputTemplateProps<
T = any,
Expand Down Expand Up @@ -1143,8 +1144,8 @@ export type UiSchema<
* When using a function, it receives the item data, index, and optionally the form context as parameters.
*/
items?:
| UiSchema<ArrayElement<T>, S, F>
| ((itemData: ArrayElement<T>, index: number, formContext?: F) => UiSchema<ArrayElement<T>, S, F>);
| UiSchema<ArrayElement<T>, S, F>
| ((itemData: ArrayElement<T>, index: number, formContext?: F) => UiSchema<ArrayElement<T>, S, F>);
};

/** A `CustomValidator` function takes in a `formData`, `errors`, `uiSchema` and `errorSchema` objects and returns the given `errors`
Expand Down
17 changes: 5 additions & 12 deletions packages/utils/test/getWidget.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { forwardRef, memo, ForwardedRef } from 'react';
import { render } from '@testing-library/react';

import { FieldPathId, Registry, RJSFSchema, WidgetProps, getWidget, Widget } from '../src';
import { FieldPathId, Registry, RJSFSchema, WidgetProps, getWidget } from '../src';

const subschema: RJSFSchema = {
type: 'boolean',
Expand Down Expand Up @@ -30,7 +30,7 @@ const schema: RJSFSchema = {
};
const schemaStr = JSON.stringify(schema);

const TestRefWidget: Widget = forwardRef<HTMLSpanElement, Partial<WidgetProps>>(function TestRefWidget(
const TestRefWidget = forwardRef<HTMLSpanElement, Partial<WidgetProps>>(function TestRefWidget(
props: Partial<WidgetProps>,
ref: ForwardedRef<HTMLSpanElement>,
) {
Expand All @@ -42,27 +42,20 @@ const TestRefWidget: Widget = forwardRef<HTMLSpanElement, Partial<WidgetProps>>(
);
});

TestRefWidget.defaultProps = {
options: { id: 'test-id' },
};
TestRefWidget.defaultProps = { options: { id: 'test-id' } };

function TestWidget(props: WidgetProps) {
Comment on lines +45 to 47
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We would then also drop testing with .defaultProps and instead roll that into the widget implementation instead in a manner similar to the changes to line 54 below

const { options } = props;
const allOptions = { id: 'test-id', ...options };
Suggested change
TestRefWidget.defaultProps = { options: { id: 'test-id' } };
function TestWidget(props: WidgetProps) {
function TestWidget(props: WidgetProps) {

const { options } = props;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Convert defaultProps into a destructure default assignment

Suggested change
const { options } = props;
const { options, id = 'foo' } = props;

return <div {...options}>test</div>;
}

TestWidget.defaultProps = {
id: 'foo',
};

function TestWidgetDefaults(props: WidgetProps) {
const { options } = props;
return <div {...options}>test</div>;
}
Comment on lines 52 to 55
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The replacement fordefaultProps.options merging

Suggested change
function TestWidgetDefaults(props: WidgetProps) {
const { options } = props;
return <div {...options}>test</div>;
}
function TestWidgetDefaults(props: WidgetProps) {
const { options } = props;
const allOptions = { color: 'yellow', ...options };
return <div {...allOptions}>test</div>;
}


TestWidgetDefaults.defaultProps = {
options: { color: 'yellow' },
};
TestWidgetDefaults.defaultProps = { options: { color: 'yellow' } };


const widgetProps: WidgetProps = {
Comment on lines +57 to 60
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
TestWidgetDefaults.defaultProps = { options: { color: 'yellow' } };
const widgetProps: WidgetProps = {
const widgetProps: WidgetProps = {

id: '',
Expand Down