diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2811b5ab22..6b630106a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/packages/docs/docs/advanced-customization/custom-widgets-fields.md b/packages/docs/docs/advanced-customization/custom-widgets-fields.md
index 139d7b86ee..68937d857f 100644
--- a/packages/docs/docs/advanced-customization/custom-widgets-fields.md
+++ b/packages/docs/docs/advanced-customization/custom-widgets-fields.md
@@ -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 ;
-}
+};
MyCustomWidget.defaultProps = {
options: {
@@ -283,6 +283,7 @@ const uiSchema: UiSchema = {
render(
, document.getElementById('app'));
```
+
> Note: This also applies to [registered custom components](#custom-component-registration).
### Customizing widgets' text input
diff --git a/packages/docs/docs/migration-guides/v7.x upgrade guide.md b/packages/docs/docs/migration-guides/v7.x upgrade guide.md
new file mode 100644
index 0000000000..45fbb17848
--- /dev/null
+++ b/packages/docs/docs/migration-guides/v7.x upgrade guide.md
@@ -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.
diff --git a/packages/utils/src/getWidget.tsx b/packages/utils/src/getWidget.tsx
index 06f23be5db..8bd38cec49 100644
--- a/packages/utils/src/getWidget.tsx
+++ b/packages/utils/src/getWidget.tsx
@@ -1,4 +1,3 @@
-import { createElement } from 'react';
import ReactIs from 'react-is';
import get from 'lodash/get';
import set from 'lodash/set';
@@ -101,11 +100,9 @@ export default function getWidget {
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(widget as Widget);
}
diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts
index 21038c491b..d66a207861 100644
--- a/packages/utils/src/types.ts
+++ b/packages/utils/src/types.ts
@@ -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 extends Array
- ? FieldPath & {
- [i: number]: PathSchema;
- }
- : T extends GenericObjectType
- ? FieldPath & {
- /** The set of names for fields in the recursive object structure */
- [key in keyof T]?: PathSchema;
- }
- : FieldPath;
+ ? FieldPath & {
+ [i: number]: PathSchema;
+ }
+ : T extends GenericObjectType
+ ? FieldPath & {
+ /** The set of names for fields in the recursive object structure */
+ [key in keyof T]?: PathSchema;
+ }
+ : FieldPath;
/** The type for error produced by RJSF schema validation */
export type RJSFValidationError = {
@@ -480,8 +480,8 @@ export interface Registry
extends GenericObjectType,
- RJSFBaseProps,
- Pick, Exclude, 'onBlur' | 'onFocus' | 'onChange'>> {
+ RJSFBaseProps,
+ Pick, Exclude, 'onBlur' | 'onFocus' | 'onChange'>> {
/** The FieldPathId of the field in the hierarchy */
fieldPathId: FieldPathId;
/** The data for this field */
@@ -857,25 +857,25 @@ export type WrapIfAdditionalTemplateProps<
/** The field or widget component instance for this field row */
children: ReactNode;
} & Pick<
- FieldTemplateProps,
- | 'id'
- | 'classNames'
- | 'hideError'
- | 'rawDescription'
- | 'rawErrors'
- | 'style'
- | 'displayLabel'
- | 'label'
- | 'required'
- | 'readonly'
- | 'disabled'
- | 'schema'
- | 'uiSchema'
- | 'onKeyRename'
- | 'onKeyRenameBlur'
- | 'onRemoveProperty'
- | 'registry'
- >;
+ FieldTemplateProps,
+ | '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<
@@ -892,8 +892,8 @@ export interface MultiSchemaFieldTemplateProps<
/** The properties that are passed to a `Widget` implementation */
export interface WidgetProps
extends GenericObjectType,
- RJSFBaseProps,
- Pick, Exclude, 'onBlur' | 'onFocus' | 'onChange'>> {
+ RJSFBaseProps,
+ Pick, Exclude, '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
*/
@@ -948,6 +948,7 @@ export type Widget
>;
+
/** The properties that are passed to the BaseInputTemplate */
export interface BaseInputTemplateProps<
T = any,
@@ -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, S, F>
- | ((itemData: ArrayElement, index: number, formContext?: F) => UiSchema, S, F>);
+ | UiSchema, S, F>
+ | ((itemData: ArrayElement, index: number, formContext?: F) => UiSchema, S, F>);
};
/** A `CustomValidator` function takes in a `formData`, `errors`, `uiSchema` and `errorSchema` objects and returns the given `errors`
diff --git a/packages/utils/test/getWidget.test.tsx b/packages/utils/test/getWidget.test.tsx
index b0a8d174c0..f86ef3abac 100644
--- a/packages/utils/test/getWidget.test.tsx
+++ b/packages/utils/test/getWidget.test.tsx
@@ -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',
@@ -30,7 +30,7 @@ const schema: RJSFSchema = {
};
const schemaStr = JSON.stringify(schema);
-const TestRefWidget: Widget = forwardRef>(function TestRefWidget(
+const TestRefWidget = forwardRef>(function TestRefWidget(
props: Partial,
ref: ForwardedRef,
) {
@@ -42,27 +42,20 @@ const TestRefWidget: Widget = forwardRef>(
);
});
-TestRefWidget.defaultProps = {
- options: { id: 'test-id' },
-};
+TestRefWidget.defaultProps = { options: { id: 'test-id' } };
function TestWidget(props: WidgetProps) {
const { options } = props;
return