From ec120e4d479a01f2c5c15d1aed7392c65970e18b Mon Sep 17 00:00:00 2001 From: Chathura Ambegoda Date: Sat, 14 Jun 2025 15:19:32 +1000 Subject: [PATCH 01/24] feat(ArrayField): add dynamic uiSchema function support Enhances the ArrayField component to support dynamic uiSchema generation through a function-based approach, allowing UI schemas to be conditionally created based on: - Item data content - Item index - Form context This maintains backward compatibility with existing static uiSchema objects while enabling more flexible, context-aware UI rendering for array items. Adds comprehensive documentation with examples showing practical use cases and benefits. --- DYNAMIC_UISCHEMA_EXAMPLES.md | 128 ++++++++++++++++++ .../core/src/components/fields/ArrayField.tsx | 20 ++- packages/utils/src/types.ts | 5 + 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 DYNAMIC_UISCHEMA_EXAMPLES.md diff --git a/DYNAMIC_UISCHEMA_EXAMPLES.md b/DYNAMIC_UISCHEMA_EXAMPLES.md new file mode 100644 index 0000000000..ef55a30820 --- /dev/null +++ b/DYNAMIC_UISCHEMA_EXAMPLES.md @@ -0,0 +1,128 @@ +# 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) => { + // 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' } + }; + } + } +}; +``` + +## 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 crashes if the function throws an error +5. **Performant**: Function is called only when rendering, same as existing custom field solutions \ No newline at end of file diff --git a/packages/core/src/components/fields/ArrayField.tsx b/packages/core/src/components/fields/ArrayField.tsx index 72825d2a03..ca701dc71e 100644 --- a/packages/core/src/components/fields/ArrayField.tsx +++ b/packages/core/src/components/fields/ArrayField.tsx @@ -500,6 +500,24 @@ class ArrayField) : undefined; const itemIdPrefix = idSchema.$id + idSeparator + index; const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator); + + // Compute the item UI schema - either use the static object or call the function + let itemUiSchema: UiSchema; + if (typeof uiSchema.items === 'function') { + try { + // Call the function with item data, index, and form context + // Note: The function is typed to receive individual item data (T), not the array (T[]) + itemUiSchema = (uiSchema.items as any)(item, index, formContext); + } catch (e) { + console.error(`Error executing dynamic uiSchema.items function for index ${index}:`, e); + // Fall back to empty object to allow the field to still render + itemUiSchema = {}; + } + } else { + // Static object case + itemUiSchema = uiSchema.items || {}; + } + return this.renderArrayFieldItem({ key, index, @@ -512,7 +530,7 @@ class ArrayField; + /** The uiSchema for items in an array. Can be an object for a uniform uiSchema across all items (current behavior), + * or a function that returns a dynamic uiSchema based on the item's data and index. + * When using a function, it receives the item data, index, and optionally the form context as parameters. + */ + items?: UiSchema | ((itemData: T, index: number, formContext?: F) => UiSchema); }; /** A `CustomValidator` function takes in a `formData`, `errors` and `uiSchema` objects and returns the given `errors` From 70701270d698cf4b88c09e7ba4b31eb9e9770b76 Mon Sep 17 00:00:00 2001 From: Chathura Ambegoda Date: Sat, 14 Jun 2025 16:00:11 +1000 Subject: [PATCH 02/24] feat(ArrayField): implement dynamic uiSchema.items function support Enhances ArrayField component to properly handle dynamic uiSchema.items functions: - Improves type safety with new ArrayElement utility type - Adds proper function call handling for both normal and fixed arrays - Ensures graceful error handling and fallback to empty object - Correctly passes item data, index and formContext to function - Adds comprehensive test suite for dynamic uiSchema functionality This feature allows for conditional UI rendering based on item data, significantly improving form customization capabilities. --- .../core/src/components/fields/ArrayField.tsx | 40 +- packages/core/test/ArrayField.test.jsx | 364 ++++++++++++++++++ packages/utils/src/types.ts | 11 +- 3 files changed, 405 insertions(+), 10 deletions(-) diff --git a/packages/core/src/components/fields/ArrayField.tsx b/packages/core/src/components/fields/ArrayField.tsx index ca701dc71e..8a0b397ae2 100644 --- a/packages/core/src/components/fields/ArrayField.tsx +++ b/packages/core/src/components/fields/ArrayField.tsx @@ -506,16 +506,18 @@ class ArrayField; } catch (e) { console.error(`Error executing dynamic uiSchema.items function for index ${index}:`, e); // Fall back to empty object to allow the field to still render - itemUiSchema = {}; + itemUiSchema = {} as UiSchema; } } else { // Static object case - itemUiSchema = uiSchema.items || {}; + itemUiSchema = (uiSchema.items || {}) as UiSchema; } return this.renderArrayFieldItem({ @@ -769,11 +771,31 @@ class ArrayField; + if (additional) { + // For additional items, use additionalItems uiSchema + itemUiSchema = (uiSchema.additionalItems || {}) as UiSchema; + } else { + // For fixed items, uiSchema.items can be an array, a function, or a single object + if (Array.isArray(uiSchema.items)) { + itemUiSchema = (uiSchema.items[index] || {}) as UiSchema; + } else if (typeof uiSchema.items === 'function') { + try { + // Call the function with item data, index, and form context + const result = uiSchema.items(item, index, formContext); + // Ensure we have a valid object, defaulting to empty object if falsy + itemUiSchema = (result || {}) as UiSchema; + } catch (e) { + console.error(`Error executing dynamic uiSchema.items function for fixed array index ${index}:`, e); + // Fall back to empty object to allow the field to still render + itemUiSchema = {} as UiSchema; + } + } else { + // Static object case + itemUiSchema = (uiSchema.items || {}) as UiSchema; + } + } const itemErrorSchema = errorSchema ? (errorSchema[index] as ErrorSchema) : undefined; return this.renderArrayFieldItem({ diff --git a/packages/core/test/ArrayField.test.jsx b/packages/core/test/ArrayField.test.jsx index 6794e23272..f7e5bbda67 100644 --- a/packages/core/test/ArrayField.test.jsx +++ b/packages/core/test/ArrayField.test.jsx @@ -3382,4 +3382,368 @@ describe('ArrayField', () => { expect(errorMessages).to.have.length(0); }); }); + + describe('Dynamic uiSchema.items function', () => { + it('should support static uiSchema.items object for backward compatibility', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + }, + }, + }; + + const uiSchema = { + items: { + name: { + 'ui:widget': 'textarea', + }, + }, + }; + + const formData = [ + { name: 'John', age: 30 }, + { name: 'Jane', age: 25 }, + ]; + + const { node } = createFormComponent({ schema, uiSchema, formData }); + + // Should render textareas for name fields based on static uiSchema + const textareas = node.querySelectorAll('textarea'); + expect(textareas).to.have.length(2); + }); + + it('should call dynamic uiSchema.items function with correct parameters', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + role: { type: 'string' }, + }, + }, + }; + + const dynamicUiSchemaFunction = sinon.spy((itemData) => { + return { + name: { + 'ui:widget': itemData.role === 'admin' ? 'textarea' : 'text', + }, + }; + }); + + const uiSchema = { + items: dynamicUiSchemaFunction, + }; + + const formData = [ + { name: 'John', role: 'admin' }, + { name: 'Jane', role: 'user' }, + ]; + + const formContext = { testContext: 'value' }; + + createFormComponent({ schema, uiSchema, formData, formContext }); + + // Should be called twice (once for each array item) + expect(dynamicUiSchemaFunction.callCount).to.equal(2); + + // Check first call + expect(dynamicUiSchemaFunction.firstCall.args[0]).to.deep.equal({ name: 'John', role: 'admin' }); + expect(dynamicUiSchemaFunction.firstCall.args[1]).to.equal(0); + expect(dynamicUiSchemaFunction.firstCall.args[2]).to.deep.equal({ testContext: 'value' }); + + // Check second call + expect(dynamicUiSchemaFunction.secondCall.args[0]).to.deep.equal({ name: 'Jane', role: 'user' }); + expect(dynamicUiSchemaFunction.secondCall.args[1]).to.equal(1); + expect(dynamicUiSchemaFunction.secondCall.args[2]).to.deep.equal({ testContext: 'value' }); + }); + + it('should apply dynamic uiSchema correctly based on item data', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + priority: { type: 'string', enum: ['high', 'normal', 'low'] }, + }, + }, + }; + + const uiSchema = { + items: (itemData) => { + if (itemData.priority === 'high') { + return { + name: { + 'ui:widget': 'textarea', + 'ui:options': { + rows: 5, + }, + }, + priority: { + 'ui:widget': 'select', + 'ui:classNames': 'priority-high', + }, + }; + } + return { + name: { + 'ui:widget': 'text', + }, + }; + }, + }; + + const formData = [ + { name: 'Critical Task', priority: 'high' }, + { name: 'Regular Task', priority: 'normal' }, + ]; + + const { node } = createFormComponent({ schema, uiSchema, formData }); + + // First item should have textarea due to high priority + const firstItemTextarea = node.querySelectorAll('.rjsf-array-item')[0].querySelector('textarea'); + expect(firstItemTextarea).to.exist; + expect(firstItemTextarea.rows).to.equal(5); + + // Second item should have text input + const secondItemInput = node.querySelectorAll('.rjsf-array-item')[1].querySelector('input[type="text"]'); + expect(secondItemInput).to.exist; + + // High priority item should have custom className + const highPrioritySelect = node.querySelector('.priority-high select'); + expect(highPrioritySelect).to.exist; + }); + + it('should handle errors in dynamic uiSchema function gracefully', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }; + + const consoleErrorStub = sinon.stub(console, 'error'); + + const uiSchema = { + items: (itemData, index) => { + if (index === 1) { + throw new Error('Test error'); + } + return { + name: { + 'ui:widget': 'textarea', + }, + }; + }, + }; + + const formData = [{ name: 'First' }, { name: 'Second' }, { name: 'Third' }]; + + const { node } = createFormComponent({ schema, uiSchema, formData }); + + // Should log error for second item + expect(consoleErrorStub.calledWith('Error executing dynamic uiSchema.items function for index 1:')).to.be.true; + + // All items should still render (with fallback for errored item) + const arrayItems = node.querySelectorAll('.rjsf-array-item'); + expect(arrayItems).to.have.length(3); + + // First and third items should have textareas + expect(arrayItems[0].querySelector('textarea')).to.exist; + expect(arrayItems[2].querySelector('textarea')).to.exist; + + // Second item should fall back to default text input + expect(arrayItems[1].querySelector('input[type="text"]')).to.exist; + expect(arrayItems[1].querySelector('textarea')).to.not.exist; + + consoleErrorStub.restore(); + }); + + it('should handle falsy return values from dynamic uiSchema function', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + visible: { type: 'boolean' }, + }, + }, + }; + + const uiSchema = { + items: (itemData) => { + // Return null/undefined for items where visible is false + if (!itemData.visible) { + return null; + } + return { + name: { + 'ui:widget': 'textarea', + }, + }; + }, + }; + + const formData = [ + { name: 'Visible Item', visible: true }, + { name: 'Hidden Item', visible: false }, + ]; + + const { node } = createFormComponent({ schema, uiSchema, formData }); + + // Both items should render + const arrayItems = node.querySelectorAll('.rjsf-array-item'); + expect(arrayItems).to.have.length(2); + + // First item should have textarea + expect(arrayItems[0].querySelector('textarea')).to.exist; + + // Second item should have default input (falsy return handled gracefully) + expect(arrayItems[1].querySelector('input[type="text"]')).to.exist; + expect(arrayItems[1].querySelector('textarea')).to.not.exist; + }); + + it('should work with empty arrays', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }; + + const dynamicUiSchemaFunction = sinon.spy(() => ({ + name: { + 'ui:widget': 'textarea', + }, + })); + + const uiSchema = { + items: dynamicUiSchemaFunction, + }; + + const formData = []; + + const { node } = createFormComponent({ schema, uiSchema, formData }); + + // Function should not be called for empty array + expect(dynamicUiSchemaFunction.callCount).to.equal(0); + + // Should still render the add button + const addButton = node.querySelector('.rjsf-array-item-add button'); + expect(addButton).to.exist; + }); + + it('should update dynamically when array items are added', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }; + + let callCount = 0; + const uiSchema = { + items: (itemData, index) => { + callCount++; + return { + name: { + 'ui:widget': 'textarea', + 'ui:placeholder': `Item ${index + 1}`, + }, + }; + }, + }; + + const formData = [{ name: 'First' }]; + + const { node } = createFormComponent({ schema, uiSchema, formData }); + + // Initial render should call function once + expect(callCount).to.equal(1); + + // Add a new item + const addButton = node.querySelector('.rjsf-array-item-add button'); + act(() => { + fireEvent.click(addButton); + }); + + // Should now have called function for both items (3 total: 1 initial + 2 for re-render) + expect(callCount).to.be.at.least(3); + + // Check placeholders are set correctly + const textareas = node.querySelectorAll('textarea'); + expect(textareas).to.have.length(2); + expect(textareas[0].placeholder).to.equal('Item 1'); + expect(textareas[1].placeholder).to.equal('Item 2'); + }); + + it('should work with nested arrays', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + title: { type: 'string' }, + tags: { + type: 'array', + items: { type: 'string' }, + }, + }, + }, + }; + + const uiSchema = { + items: (itemData, index) => ({ + title: { + 'ui:widget': index === 0 ? 'textarea' : 'text', + }, + tags: { + items: { + 'ui:widget': 'text', + 'ui:placeholder': 'Tag', + }, + }, + }), + }; + + const formData = [ + { title: 'First Post', tags: ['react', 'form'] }, + { title: 'Second Post', tags: ['javascript'] }, + ]; + + const { node } = createFormComponent({ schema, uiSchema, formData }); + + // First item title should be textarea + const firstItemTitle = node + .querySelectorAll('.rjsf-array-item')[0] + .querySelector('.rjsf-field-object .rjsf-field-string:first-of-type textarea'); + expect(firstItemTitle).to.exist; + + // Second item title should be text input + const secondItemTitle = node + .querySelectorAll('.rjsf-array-item')[1] + .querySelector('.rjsf-field-object .rjsf-field-string:first-of-type input[type="text"]'); + expect(secondItemTitle).to.exist; + + // Verify that tag inputs exist + const tagInputs = node.querySelectorAll('.rjsf-field-array .rjsf-field-array input[type="text"]'); + expect(tagInputs.length).to.be.at.least(3); // 2 tags in first item + 1 tag in second item + }); + }); }); diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index dea22d92a0..9b37add46b 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -1003,6 +1003,13 @@ export type UIOptionsType< [key: string]: boolean | number | string | object | any[] | null | undefined; }; +/** + * A utility type that extracts the element type from an array type. + * If the type is not an array, it returns the type itself as a safe fallback. + * Handles both standard arrays and readonly arrays. + */ +export type ArrayElement = A extends readonly (infer E)[] ? E : A; + /** Type describing the well-known properties of the `UiSchema` while also supporting all user defined properties, * starting with `ui:`. */ @@ -1032,7 +1039,9 @@ export type UiSchema< * or a function that returns a dynamic uiSchema based on the item's data and index. * When using a function, it receives the item data, index, and optionally the form context as parameters. */ - items?: UiSchema | ((itemData: T, index: number, formContext?: F) => UiSchema); + items?: + | UiSchema, S, F> + | ((itemData: ArrayElement, index: number, formContext?: F) => UiSchema, S, F>); }; /** A `CustomValidator` function takes in a `formData`, `errors` and `uiSchema` objects and returns the given `errors` From b06412985c8ef097807aef7d4a7bde6a3bdcceca Mon Sep 17 00:00:00 2001 From: Chathura Ambegoda Date: Sat, 14 Jun 2025 16:07:26 +1000 Subject: [PATCH 03/24] fix: improve error messages for dynamic uiSchema functions - Clarifies error messages by adding "item at" to provide better context when dynamic uiSchema functions fail - Adds test case for handling errors in dynamic uiSchema functions for fixed arrays - Ensures consistent error handling between regular and fixed array implementations This improves debugging experience for developers using dynamic uiSchema functionality. --- .../core/src/components/fields/ArrayField.tsx | 4 +- packages/core/test/ArrayField.test.jsx | 41 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/fields/ArrayField.tsx b/packages/core/src/components/fields/ArrayField.tsx index 8a0b397ae2..521e0185c8 100644 --- a/packages/core/src/components/fields/ArrayField.tsx +++ b/packages/core/src/components/fields/ArrayField.tsx @@ -511,7 +511,7 @@ class ArrayField; } catch (e) { - console.error(`Error executing dynamic uiSchema.items function for index ${index}:`, e); + console.error(`Error executing dynamic uiSchema.items function for item at index ${index}:`, e); // Fall back to empty object to allow the field to still render itemUiSchema = {} as UiSchema; } @@ -787,7 +787,7 @@ class ArrayField; } catch (e) { - console.error(`Error executing dynamic uiSchema.items function for fixed array index ${index}:`, e); + console.error(`Error executing dynamic uiSchema.items function for item at index ${index}:`, e); // Fall back to empty object to allow the field to still render itemUiSchema = {} as UiSchema; } diff --git a/packages/core/test/ArrayField.test.jsx b/packages/core/test/ArrayField.test.jsx index f7e5bbda67..86b87166ce 100644 --- a/packages/core/test/ArrayField.test.jsx +++ b/packages/core/test/ArrayField.test.jsx @@ -3551,7 +3551,8 @@ describe('ArrayField', () => { const { node } = createFormComponent({ schema, uiSchema, formData }); // Should log error for second item - expect(consoleErrorStub.calledWith('Error executing dynamic uiSchema.items function for index 1:')).to.be.true; + expect(consoleErrorStub.calledWith('Error executing dynamic uiSchema.items function for item at index 1:')).to.be + .true; // All items should still render (with fallback for errored item) const arrayItems = node.querySelectorAll('.rjsf-array-item'); @@ -3568,6 +3569,44 @@ describe('ArrayField', () => { consoleErrorStub.restore(); }); + it('should handle errors in dynamic uiSchema function gracefully for fixed arrays', () => { + const schema = { + type: 'array', + items: [{ type: 'string' }, { type: 'string' }], + }; + + const consoleErrorStub = sinon.stub(console, 'error'); + + const uiSchema = { + items: (itemData, index) => { + if (index === 1) { + throw new Error('Test error in fixed array'); + } + return { 'ui:widget': 'textarea' }; + }, + }; + + const formData = ['First', 'Second']; + const { node } = createFormComponent({ schema, uiSchema, formData }); + + // Should log error for second item + expect(consoleErrorStub.calledWith('Error executing dynamic uiSchema.items function for item at index 1:')).to.be + .true; + + // All items should still render + const arrayItems = node.querySelectorAll('.rjsf-array-item'); + expect(arrayItems).to.have.length(2); + + // First item should have textarea + expect(arrayItems[0].querySelector('textarea')).to.exist; + + // Second item should fall back to default text input + expect(arrayItems[1].querySelector('input[type="text"]')).to.exist; + expect(arrayItems[1].querySelector('textarea')).to.not.exist; + + consoleErrorStub.restore(); + }); + it('should handle falsy return values from dynamic uiSchema function', () => { const schema = { type: 'array', From e21fab95fbb867ed83c743b4c6816bfb27205474 Mon Sep 17 00:00:00 2001 From: Chathura Ambegoda Date: Sat, 14 Jun 2025 16:22:45 +1000 Subject: [PATCH 04/24] fix(ArrayField): allow undefined UI schema for array items Updates the type annotations in ArrayField component to accept undefined as a valid type for itemUiSchema. This preserves backward compatibility by not defaulting to an empty object when uiSchema.items is undefined. Previously, the code always defaulted to an empty object which could cause unexpected behavior when checking for the existence of UI schema. --- packages/core/src/components/fields/ArrayField.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/fields/ArrayField.tsx b/packages/core/src/components/fields/ArrayField.tsx index 521e0185c8..98357ab4f6 100644 --- a/packages/core/src/components/fields/ArrayField.tsx +++ b/packages/core/src/components/fields/ArrayField.tsx @@ -502,7 +502,7 @@ class ArrayField; + let itemUiSchema: UiSchema | undefined; if (typeof uiSchema.items === 'function') { try { // Call the function with item data, index, and form context @@ -516,8 +516,8 @@ class ArrayField; } } else { - // Static object case - itemUiSchema = (uiSchema.items || {}) as UiSchema; + // Static object case - preserve undefined to maintain backward compatibility + itemUiSchema = uiSchema.items as UiSchema | undefined; } return this.renderArrayFieldItem({ @@ -772,7 +772,7 @@ class ArrayField; + let itemUiSchema: UiSchema | undefined; if (additional) { // For additional items, use additionalItems uiSchema itemUiSchema = (uiSchema.additionalItems || {}) as UiSchema; @@ -851,7 +851,7 @@ class ArrayField; + itemUiSchema: UiSchema | undefined; itemIdSchema: IdSchema; itemErrorSchema?: ErrorSchema; autofocus?: boolean; From 6da9a53c9f452c46221c58433ce440115b35eec7 Mon Sep 17 00:00:00 2001 From: Chathura Ambegoda Date: Sat, 14 Jun 2025 16:34:19 +1000 Subject: [PATCH 05/24] fix: preserve undefined values in uiSchema handling Modifies the ArrayField component to properly handle undefined uiSchema values instead of converting them to empty objects. This maintains backward compatibility and prevents unnecessary props from being passed to child components. The change: - Only applies truthy results from dynamic uiSchema functions - Preserves undefined values in static object cases - Updates snapshots to reflect the removed empty uiSchema objects This fixes inconsistent behavior when working with conditionally defined UI schemas. --- .../test/__snapshots__/Array.test.tsx.snap | 44 +++++++++---------- .../core/src/components/fields/ArrayField.tsx | 22 +++++----- .../test/__snapshots__/Array.test.tsx.snap | 6 --- .../test/__snapshots__/Array.test.tsx.snap | 18 ++++---- 4 files changed, 42 insertions(+), 48 deletions(-) diff --git a/packages/chakra-ui/test/__snapshots__/Array.test.tsx.snap b/packages/chakra-ui/test/__snapshots__/Array.test.tsx.snap index 0c91a67fb2..54d7beac94 100644 --- a/packages/chakra-ui/test/__snapshots__/Array.test.tsx.snap +++ b/packages/chakra-ui/test/__snapshots__/Array.test.tsx.snap @@ -2349,7 +2349,7 @@ exports[`array fields fixed array 1`] = ` className="chakra-field__root emotion-9" data-part="root" data-scope="field" - id="field:::r3:" + id="field:::r2:" role="group" >