-
Notifications
You must be signed in to change notification settings - Fork 2.3k
feat: Add dynamic uiSchema support for array items #4675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
heath-freenome
merged 34 commits into
rjsf-team:main
from
chathuraa:feat/dynamic-uischema-array-items
Aug 12, 2025
Merged
Changes from 10 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
ec120e4
feat(ArrayField): add dynamic uiSchema function support
chathuraa 7070127
feat(ArrayField): implement dynamic uiSchema.items function support
chathuraa b064129
fix: improve error messages for dynamic uiSchema functions
chathuraa e21fab9
fix(ArrayField): allow undefined UI schema for array items
chathuraa 6da9a53
fix: preserve undefined values in uiSchema handling
chathuraa 4c45993
fix: add id to Fieldset component in ArrayFieldTemplate
chathuraa 0000a02
docs: enhance dynamic uiSchema documentation with examples and best p…
chathuraa cc646d8
Merge remote-tracking branch 'origin/main' into feat/dynamic-uischema…
chathuraa c880f1f
fix: replace nanoid with lodash/uniqueId and handle null uiSchema
chathuraa 873059b
fix: correct CheckboxWidget event handlers and enhance getUiOptions
chathuraa 80e0206
refactor(ArrayField): extract UI schema computation logic and fix che…
chathuraa fea1569
fix: pass checkbox state in onFocus and onBlur callbacks
chathuraa 0c877a2
Merge branch 'main' into feat/dynamic-uischema-array-items
chathuraa 6fd9469
Merge remote-tracking branch 'fork/feat/dynamic-uischema-array-items'…
chathuraa 96bdcf4
Merge branch 'main' into feat/dynamic-uischema-array-items
chathuraa 5280e5a
Merge remote-tracking branch 'origin/main' into feat/dynamic-uischema…
chathuraa bfe373d
fix: ensure validator passed in Form rerenders & prevent null schema …
chathuraa 32ff5b0
fix: use current value instead of event target in checkbox onFocus/on…
chathuraa 2ebf860
Merge branch 'main' into feat/dynamic-uischema-array-items
chathuraa a5760f6
Merge branch 'main' into feat/dynamic-uischema-array-items
chathuraa 41c079b
docs: updated the docs - added support for dynamic UI schema in array…
chathuraa 1c4ac14
Merge remote-tracking branch 'fork/feat/dynamic-uischema-array-items'…
chathuraa 7d08762
Merge branch 'main' into feat/dynamic-uischema-array-items
chathuraa 051a7f5
docs(migration): add documentation for dynamic UI schema for array items
chathuraa 2826971
Merge remote-tracking branch 'fork/feat/dynamic-uischema-array-items'…
chathuraa 8f57e97
Update CHANGELOG.md
chathuraa 9563af5
Update packages/docs/docs/migration-guides/v6.x upgrade guide.md
chathuraa 1300100
Update packages/docs/docs/migration-guides/v6.x upgrade guide.md
chathuraa d10c54c
Update packages/docs/docs/json-schema/arrays.md
chathuraa b8a385a
Update packages/docs/docs/api-reference/dynamic-ui-schema-examples.md
chathuraa 3a4bc80
Update packages/docs/docs/api-reference/dynamic-ui-schema-examples.md
chathuraa b068a2c
Update packages/docs/docs/api-reference/uiSchema.md
chathuraa 4e96ecc
Update packages/docs/docs/api-reference/dynamic-ui-schema-examples.md
chathuraa 0d4a931
docs: added about correcting checkbox widget focus handlers across al…
chathuraa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| # Dynamic uiSchema Examples | ||
|
|
||
| ## Backward Compatibility Examples | ||
|
|
||
| ### Example 1: Traditional Static uiSchema (No Changes Required) | ||
|
|
||
| ```javascript | ||
| // This continues to work exactly as before | ||
| const uiSchema = { | ||
| guests: { | ||
| items: { | ||
| name: { 'ui:placeholder': 'Enter guest name' }, | ||
| age: { 'ui:widget': 'updown' }, | ||
| relationship: { 'ui:widget': 'select' } | ||
| } | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ### Example 2: Dynamic uiSchema with Function | ||
|
|
||
| ```javascript | ||
| // New functionality - dynamic UI based on item data | ||
| const uiSchema = { | ||
| guests: { | ||
| items: (itemData, index, formContext) => { | ||
| // Note: For newly added items, `itemData` will be undefined or contain default values. | ||
| // Using optional chaining (`?.`) is recommended to handle this case gracefully. | ||
|
|
||
| // Base UI schema for all items | ||
| const baseUiSchema = { | ||
| name: { 'ui:placeholder': `Guest ${index + 1} name` }, | ||
| relationship: { 'ui:widget': 'select' } | ||
| }; | ||
|
|
||
| // Conditionally modify UI based on data | ||
| if (itemData?.relationship === 'child') { | ||
| return { | ||
| ...baseUiSchema, | ||
| age: { | ||
| 'ui:widget': 'updown', | ||
| 'ui:help': 'Age is required for children', | ||
| 'ui:options': { min: 0, max: 17 } | ||
| }, | ||
| guardianName: { | ||
| 'ui:placeholder': 'Parent/Guardian name' | ||
| }, | ||
| mealPreference: { 'ui:widget': 'hidden' } | ||
| }; | ||
| } | ||
|
|
||
| if (itemData?.relationship === 'adult') { | ||
| return { | ||
| ...baseUiSchema, | ||
| age: { 'ui:widget': 'hidden' }, | ||
| guardianName: { 'ui:widget': 'hidden' }, | ||
| mealPreference: { | ||
| 'ui:widget': 'select', | ||
| 'ui:placeholder': 'Select meal preference' | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| // Default for new items or unknown relationships | ||
| return baseUiSchema; | ||
| } | ||
| } | ||
| }; | ||
| ``` | ||
| ### Example 3: Using Form Context | ||
| ```javascript | ||
| const uiSchema = { | ||
| participants: { | ||
| items: (itemData, index, formContext) => { | ||
| // Access form-wide settings | ||
| const isConference = formContext?.eventType === 'conference'; | ||
|
|
||
| return { | ||
| name: { 'ui:placeholder': 'Participant name' }, | ||
| email: { 'ui:widget': 'email' }, | ||
| // Show workshop selection only for conference events | ||
| workshop: isConference | ||
| ? { 'ui:widget': 'select' } | ||
| : { 'ui:widget': 'hidden' } | ||
| }; | ||
| } | ||
| } | ||
| }; | ||
| ``` | ||
| ### Example 4: Falsy Return Values | ||
| ```javascript | ||
| const uiSchema = { | ||
| items: { | ||
| items: (itemData, index) => { | ||
| // Only apply custom UI to specific items | ||
| if (itemData?.needsCustomUI) { | ||
| return { | ||
| field1: { 'ui:widget': 'textarea' }, | ||
| field2: { 'ui:help': 'This item needs special attention' } | ||
| }; | ||
| } | ||
|
|
||
| // Return null or undefined to use default UI rendering | ||
| // This is useful for conditionally applying custom UI | ||
| return null; | ||
| } | ||
| } | ||
| }; | ||
| ``` | ||
| ### Example 5: Dynamic UI for Fixed Arrays | ||
| For fixed/tuple arrays (where schema.items is an array), the dynamic function can be applied to each position: | ||
| ```javascript | ||
| const schema = { | ||
| type: 'array', | ||
| items: [ | ||
| { type: 'string', title: 'First Name' }, | ||
| { type: 'string', title: 'Last Name' }, | ||
| { type: 'object', title: 'Details', properties: { age: { type: 'number' }, role: { type: 'string' } } } | ||
| ] | ||
| }; | ||
|
|
||
| const uiSchema = { | ||
| items: [ | ||
| { 'ui:placeholder': 'Enter first name' }, // Static UI for first item | ||
| { 'ui:placeholder': 'Enter last name' }, // Static UI for second item | ||
| // Dynamic UI for third item based on its data | ||
| (itemData, index) => { | ||
| if (itemData?.role === 'admin') { | ||
| return { | ||
| age: { 'ui:widget': 'hidden' }, | ||
| role: { 'ui:help': 'Admin role selected' } | ||
| }; | ||
| } | ||
| return { | ||
| age: { 'ui:widget': 'updown' }, | ||
| role: { 'ui:widget': 'select' } | ||
| }; | ||
| } | ||
| ] | ||
| }; | ||
| ``` | ||
| ## Schema Example | ||
| ```javascript | ||
| const schema = { | ||
| type: 'object', | ||
| properties: { | ||
| guests: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'object', | ||
| properties: { | ||
| name: { type: 'string', title: 'Name' }, | ||
| age: { type: 'number', title: 'Age' }, | ||
| relationship: { | ||
| type: 'string', | ||
| title: 'Relationship', | ||
| enum: ['adult', 'child', 'senior'] | ||
| }, | ||
| guardianName: { type: 'string', title: 'Guardian Name' }, | ||
| mealPreference: { | ||
| type: 'string', | ||
| title: 'Meal Preference', | ||
| enum: ['vegetarian', 'vegan', 'standard', 'gluten-free'] | ||
| } | ||
| }, | ||
| required: ['name', 'relationship'] | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| ``` | ||
| ## Key Benefits | ||
| 1. **Backward Compatible**: Existing forms with object-based `uiSchema.items` continue to work without any changes | ||
| 2. **Progressive Enhancement**: Developers can opt-in to dynamic behavior when needed | ||
| 3. **Flexible**: Access to item data, index, and form context enables complex UI logic | ||
| 4. **Safe**: Built-in error handling prevents the entire form from crashing if your function throws an error. When an error occurs for a specific item, it will be caught and logged to the developer console, and the UI for that item will fall back to the default rendering. This ensures the rest of the form remains functional while making debugging easier. | ||
| 5. **On-Demand Execution**: The function is executed on-demand during the render cycle. However, as it runs for each array item, performance should be carefully managed for large lists (see Performance Considerations below). | ||
| ## Key Behaviors | ||
| - **Falsy Returns**: If your function returns a falsy value (e.g., `null` or `undefined`), the UI for that specific item will fall back to its default rendering. This allows you to conditionally apply custom UI only when needed. | ||
| - **Error Handling**: If your function throws an error, it will be caught and logged to the console. The form will continue to work, using default UI for the affected item. | ||
| - **New Items**: When a new item is added to the array, `itemData` will be `undefined` or contain default values from the schema. Always use optional chaining (`?.`) to safely access properties. | ||
| ## Performance Considerations | ||
| When using dynamic `uiSchema.items` functions, keep in mind: | ||
| - The function is executed **on every render** for **each array item** | ||
| - For large arrays, this can impact performance if the function performs expensive operations | ||
| - Best practices: | ||
| - Keep the function logic lightweight and fast | ||
| - Avoid heavy computations or external API calls within the function | ||
| - Consider memoizing results if the same inputs produce the same outputs | ||
| - For complex logic, pre-compute values and store them in formContext or component state | ||
| Example of a performance-optimized approach: | ||
| ```javascript | ||
| // In your React component that renders the form: | ||
| const MyFormComponent = ({ schema, formData }) => { | ||
| // Pre-compute expensive data once, and only re-compute if dependencies change | ||
| const expensiveData = useMemo(() => computeExpensiveData(), [/* dependencies */]); | ||
|
|
||
| // Define the uiSchema inside the component so it can access the memoized data | ||
| const uiSchema = { | ||
| myArrayField: { // Target your specific array field | ||
| items: (itemData, index, formContext) => { | ||
| // Use the pre-computed data - this is very fast | ||
| const config = expensiveData[itemData?.type] || defaultConfig; | ||
|
|
||
| return { | ||
| field: { 'ui:widget': config.widget } | ||
| }; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| return <Form schema={schema} uiSchema={uiSchema} formData={formData} />; | ||
| }; | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.