fix: add React 19 compatibility for getWidget (defaultOptions + remov…#4912
fix: add React 19 compatibility for getWidget (defaultOptions + remov…#4912Ashutoshx7 wants to merge 4 commits intorjsf-team:mainfrom
Conversation
…e isForwardRef) fixes rjsf-team#4907 - Add support for new static defaultOptions property on widgets (React 19 compatible) - Maintain backwards compatibility with legacy defaultProps.options pattern - Replace deprecated react-is.isForwardRef() with general component detection - Update documentation and add migration guide
bfbb128 to
1dea32a
Compare
packages/docs/docs/advanced-customization/custom-widgets-fields.md
Outdated
Show resolved
Hide resolved
| * @param AWidget - A widget that will be wrapped or one that is already wrapped | ||
| * @returns - The wrapper widget | ||
| */ | ||
| function mergeWidgetOptions<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>( |
There was a problem hiding this comment.
I don't know that we want to maintain this defaultOptions pattern. Custom widget authors could implement this in their widgets if they want to. With defaultProps gone in React 19, I think we may want to just remove mergeWidgetOptions in RJSF v7. With that in mind, I don't think we should add this defaultOptions compatibility feature. @Ashutoshx7, is there any reason that I'm not realizing that explains why we need to support this in RJSF?
There was a problem hiding this comment.
@nickgros You raise a good point and I believe you are correct. I am curious to hear what @Ashutoshx7 has to say in reply to your question.
There was a problem hiding this comment.
sorry for the late reply !
The main motivation behind this change was to provide a smooth React 19 transition for existing custom widgets that currently rely on defaultProps.options, without breaking them immediately. The idea behind defaultOptions was to offer a React-19-safe replacement while keeping backward compatibility during the v7 lifecycle.
I’ll update the PR to drop the defaultOptions support and limit it to addressing the React 19 deprecations only (removing defaultProps usage and replacing react-is.isForwardRef).
I’ll push the updated changes shortly.
There was a problem hiding this comment.
Did you actually change anything in this file or is it just whitespace. If just whitespace, please revert
| // 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>); | ||
| } |
There was a problem hiding this comment.
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.
| TestRefWidget.defaultProps = { options: { id: 'test-id' } }; | ||
|
|
||
| function TestWidget(props: WidgetProps) { | ||
| const { options } = props; |
There was a problem hiding this comment.
Convert defaultProps into a destructure default assignment
| const { options } = props; | |
| const { options, id = 'foo' } = props; |
| function TestWidgetDefaults(props: WidgetProps) { | ||
| const { options } = props; | ||
| return <div {...options}>test</div>; | ||
| } |
There was a problem hiding this comment.
The replacement fordefaultProps.options merging
| 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' } }; | ||
|
|
||
|
|
||
| const widgetProps: WidgetProps = { |
There was a problem hiding this comment.
| TestWidgetDefaults.defaultProps = { options: { color: 'yellow' } }; | |
| const widgetProps: WidgetProps = { | |
| const widgetProps: WidgetProps = { |
| TestRefWidget.defaultProps = { options: { id: 'test-id' } }; | ||
|
|
||
| function TestWidget(props: WidgetProps) { |
There was a problem hiding this comment.
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 };
| TestRefWidget.defaultProps = { options: { id: 'test-id' } }; | |
| function TestWidget(props: WidgetProps) { | |
| function TestWidget(props: WidgetProps) { |
Reasons for making this change
This PR adds React 19 compatibility to getWidget.tsx by addressing two deprecated patterns:
defaultProps.options: React 19 deprecateddefaultPropsfor function components. This PR adds support for a new staticdefaultOptionsproperty while maintaining backwards compatibility with the legacy pattern.react-is.isForwardRef(): React 19 removed this function. This PR replaces it with a more general component detection check using$$typeof.fixes #4907
Changes Made
packages/utils/src/getWidget.tsx:
defaultOptionsstatic property (React 19 compatible)defaultProps.options(deprecated but still works)ReactIs.isForwardRef()with general$$typeofcheck for exotic componentspackages/utils/test/getWidget.test.tsx: Updated tests to use the new
defaultOptionspatternCHANGELOG.md: Added entry under
@rjsf/utilspackages/docs/docs/advanced-customization/custom-widgets-fields.md: Updated documentation to show new pattern
packages/docs/docs/migration-guides/v7.x upgrade guide.md: Created migration guide for the deprecation
Checklist
npx nx run-many --target=build --exclude=@rjsf/docs && npm run test:updateto update snapshots, if needed.