Skip to content

Commit bd54461

Browse files
feat: Add dynamic uiSchema support for array items (#4675)
* 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. * 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. * 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. * 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. * 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. * fix: add id to Fieldset component in ArrayFieldTemplate Propagates the idSchema.$id to the Fieldset component in the ArrayFieldTemplate, ensuring consistent and accessible ID attributes in the generated HTML. Updates test snapshots to reflect the new ID values, replacing previously null or auto-generated IDs with consistent "root" identifiers and related ARIA attribute references. * docs: enhance dynamic uiSchema documentation with examples and best practices Expands DYNAMIC_UISCHEMA_EXAMPLES.md with: - Additional examples for falsy return values and fixed arrays - Detailed notes on handling new items and error cases - Performance considerations and optimization techniques - Clarified behavior documentation for dynamic UI schemas Improves developer experience by providing comprehensive guidance for implementing dynamic UI schemas effectively. * fix: replace nanoid with lodash/uniqueId and handle null uiSchema - Replaces nanoid with lodash/uniqueId for array item ID generation - Adds prefix "rjsf-array-item-" to generated IDs for better debugging - Adds null/undefined checks for uiSchema in ArrayField and getUiOptions - Adds NX Cloud ID to configuration This change improves robustness when handling undefined UI schemas and provides more consistent ID generation for array items. * fix: correct CheckboxWidget event handlers and enhance getUiOptions - Updates CheckboxWidget _onBlur and _onFocus handlers to use target.checked instead of target.value - Adds tests for getUiOptions when uiSchema is undefined or null - Removes nxCloudId from nx.json configuration * refactor(ArrayField): extract UI schema computation logic and fix checkbox value handling Refactors ArrayField by extracting computeItemUiSchema helper method to eliminate duplicate code - Creates a shared private method for computing item UI schema that handles both function and object cases - Used in both regular arrays and fixed arrays rendering paths - Improves error handling for dynamic UI schema functions Fixes CheckboxWidget components in Fluent UI and MUI packages: - Changes onBlur/onFocus handlers to pass the checkbox checked state instead of its value Fixes getDisplayLabel to safely handle undefined uiSchema * fix: pass checkbox state in onFocus and onBlur callbacks Updates all CheckboxWidget implementations to use the actual checked state from the event target in onFocus and onBlur handlers instead of the component's value prop. This ensures callbacks receive the current checkbox state rather than potentially stale values from the component props. * fix: ensure validator passed in Form rerenders & prevent null schema errors This commit fixes two potential issues: 1. Updates `createComponent` test utility to ensure Form components always receive a validator prop when rerendering, preventing potential test failures 2. Adds null checks for rootSchema in SchemaUtils to prevent errors when schema is undefined or null * fix: use current value instead of event target in checkbox onFocus/onBlur Updates focus and blur event handlers in checkbox widgets across all UI libraries to use the current value state instead of reading from event target. This ensures consistency across implementations and fixes potential issues where the event target might not correctly reflect the current checkbox state. * docs: updated the docs - added support for dynamic UI schema in array fields The change includes: - documentation with TypeScript examples - Updated array and uiSchema documentation to reference the new capabilities Closes #4706 * docs(migration): add documentation for dynamic UI schema for array items Extends the v6.x upgrade guide to document the new feature that allows dynamic UI schema generation for array items. Includes a code example demonstrating the feature and references additional documentation. * Update CHANGELOG.md Co-authored-by: Heath C <[email protected]> * Update packages/docs/docs/migration-guides/v6.x upgrade guide.md Co-authored-by: Heath C <[email protected]> * Update packages/docs/docs/migration-guides/v6.x upgrade guide.md Co-authored-by: Heath C <[email protected]> * Update packages/docs/docs/json-schema/arrays.md Co-authored-by: Heath C <[email protected]> * Update packages/docs/docs/api-reference/dynamic-ui-schema-examples.md Co-authored-by: Heath C <[email protected]> * Update packages/docs/docs/api-reference/dynamic-ui-schema-examples.md Co-authored-by: Heath C <[email protected]> * Update packages/docs/docs/api-reference/uiSchema.md Co-authored-by: Heath C <[email protected]> * Update packages/docs/docs/api-reference/dynamic-ui-schema-examples.md Co-authored-by: Heath C <[email protected]> * docs: added about correcting checkbox widget focus handlers across all UI libraries Fixes the checkbox widget to use current value instead of event target in onFocus/onBlur handlers across all UI libraries: - core - chakra-ui - daisyui - fluentui-rc - mui - primereact - semantic-ui Fixes #4704 --------- Co-authored-by: Heath C <[email protected]>
1 parent 91d95a5 commit bd54461

File tree

22 files changed

+918
-83
lines changed

22 files changed

+918
-83
lines changed

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,47 @@ it according to semantic versioning. For example, if your PR adds a breaking cha
1515
should change the heading of the (upcoming) version to include a major version bump.
1616
1717
-->
18+
19+
# 6.0.0-beta.14
20+
21+
## @rjsf/core
22+
23+
- Added support for dynamic UI schema in array fields - the `items` property in `uiSchema` can now accept a function that returns a UI schema based on the array item's data, index, and form context ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
24+
- Fixed checkbox widget to use current value instead of event target in onFocus/onBlur handlers, fixing [#4704](https://github.com/rjsf-team/react-jsonschema-form/issues/4704)
25+
26+
## @rjsf/utils
27+
28+
- Updated `UiSchema` type to support dynamic array item UI schemas - the `items` property can now be either a `UiSchema` object or a function that returns a `UiSchema` ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
29+
30+
## @rjsf/chakra-ui
31+
32+
- Fixed checkbox widget to use current value instead of event target in onFocus/onBlur handlers, fixing [#4704](https://github.com/rjsf-team/react-jsonschema-form/issues/4704)
33+
34+
## @rjsf/daisyui
35+
36+
- Fixed checkbox widget to use current value instead of event target in onFocus/onBlur handlers, fixing [#4704](https://github.com/rjsf-team/react-jsonschema-form/issues/4704)
37+
38+
## @rjsf/fluentui-rc
39+
40+
- Fixed checkbox widget to use current value instead of event target in onFocus/onBlur handlers, fixing [#4704](https://github.com/rjsf-team/react-jsonschema-form/issues/4704)
41+
42+
## @rjsf/mui
43+
44+
- Fixed checkbox widget to use current value instead of event target in onFocus/onBlur handlers, fixing [#4704](https://github.com/rjsf-team/react-jsonschema-form/issues/4704)
45+
46+
## @rjsf/primereact
47+
48+
- Fixed checkbox widget to use current value instead of event target in onFocus/onBlur handlers, fixing [#4704](https://github.com/rjsf-team/react-jsonschema-form/issues/4704)
49+
50+
## @rjsf/semantic-ui
51+
52+
- Fixed checkbox widget to use current value instead of event target in onFocus/onBlur handlers, fixing [#4704](https://github.com/rjsf-team/react-jsonschema-form/issues/4704)
53+
54+
## Dev / docs / playground
55+
56+
- Added comprehensive documentation for dynamic UI schema feature with TypeScript examples ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
57+
- Updated array documentation to reference the new dynamic UI schema capabilities ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
58+
1859
# 6.0.0-beta.13
1960

2061
## @rjsf/shadcn

packages/chakra-ui/src/CheckboxWidget/CheckboxWidget.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ export default function CheckboxWidget<
4848
const description = options.description || schema.description;
4949

5050
const _onChange = ({ checked }: CheckboxCheckedChangeDetails) => onChange(checked);
51-
const _onBlur = ({ target }: FocusEvent<HTMLInputElement | any>) => onBlur(id, target && target.value);
52-
const _onFocus = ({ target }: FocusEvent<HTMLInputElement | any>) => onFocus(id, target && target.value);
51+
const _onBlur = ({ target }: FocusEvent<HTMLInputElement | any>) => onBlur(id, target && target.checked);
52+
const _onFocus = ({ target }: FocusEvent<HTMLInputElement | any>) => onFocus(id, target && target.checked);
5353

5454
const chakraProps = getChakra({ uiSchema });
5555

packages/core/src/components/fields/ArrayField.tsx

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import cloneDeep from 'lodash/cloneDeep';
2222
import get from 'lodash/get';
2323
import isObject from 'lodash/isObject';
2424
import set from 'lodash/set';
25-
import { nanoid } from 'nanoid';
25+
import uniqueId from 'lodash/uniqueId';
2626

2727
/** Type used to represent the keyed form data used in the state */
2828
type KeyedFormDataType<T> = { key: string; item: T };
@@ -37,7 +37,7 @@ type ArrayFieldState<T> = {
3737

3838
/** Used to generate a unique ID for an element in a row */
3939
function generateRowId() {
40-
return nanoid();
40+
return uniqueId('rjsf-array-item-');
4141
}
4242

4343
/** Converts the `formData` into `KeyedFormDataType` data, using the `generateRowId()` function to create the key
@@ -423,6 +423,39 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
423423
onChange(value, undefined, idSchema && idSchema.$id);
424424
};
425425

426+
/** Helper method to compute item UI schema for both normal and fixed arrays
427+
* Handles both static object and dynamic function cases
428+
*
429+
* @param uiSchema - The parent UI schema containing items definition
430+
* @param item - The item data
431+
* @param index - The index of the item
432+
* @param formContext - The form context
433+
* @returns The computed UI schema for the item
434+
*/
435+
private computeItemUiSchema(
436+
uiSchema: UiSchema<T[], S, F>,
437+
item: T,
438+
index: number,
439+
formContext: F,
440+
): UiSchema<T[], S, F> | undefined {
441+
if (typeof uiSchema.items === 'function') {
442+
try {
443+
// Call the function with item data, index, and form context
444+
// TypeScript now correctly infers the types thanks to the ArrayElement type in UiSchema
445+
const result = uiSchema.items(item, index, formContext);
446+
// Only use the result if it's truthy
447+
return result as UiSchema<T[], S, F>;
448+
} catch (e) {
449+
console.error(`Error executing dynamic uiSchema.items function for item at index ${index}:`, e);
450+
// Fall back to undefined to allow the field to still render
451+
return undefined;
452+
}
453+
} else {
454+
// Static object case - preserve undefined to maintain backward compatibility
455+
return uiSchema.items as UiSchema<T[], S, F> | undefined;
456+
}
457+
}
458+
426459
/** Renders the `ArrayField` depending on the specific needs of the schema and uischema elements
427460
*/
428461
render() {
@@ -500,6 +533,10 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
500533
const itemErrorSchema = errorSchema ? (errorSchema[index] as ErrorSchema<T[]>) : undefined;
501534
const itemIdPrefix = idSchema.$id + idSeparator + index;
502535
const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
536+
537+
// Compute the item UI schema using the helper method
538+
const itemUiSchema = this.computeItemUiSchema(uiSchema, item, index, formContext);
539+
503540
return this.renderArrayFieldItem({
504541
key,
505542
index,
@@ -512,7 +549,7 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
512549
itemIdSchema,
513550
itemErrorSchema,
514551
itemData: itemCast,
515-
itemUiSchema: uiSchema.items,
552+
itemUiSchema,
516553
autofocus: autofocus && index === 0,
517554
onBlur,
518555
onFocus,
@@ -751,11 +788,20 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
751788
: itemSchemas[index]) || {};
752789
const itemIdPrefix = idSchema.$id + idSeparator + index;
753790
const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
754-
const itemUiSchema = additional
755-
? uiSchema.additionalItems || {}
756-
: Array.isArray(uiSchema.items)
757-
? uiSchema.items[index]
758-
: uiSchema.items || {};
791+
// Compute the item UI schema - handle both static and dynamic cases
792+
let itemUiSchema: UiSchema<T[], S, F> | undefined;
793+
if (additional) {
794+
// For additional items, use additionalItems uiSchema
795+
itemUiSchema = uiSchema.additionalItems as UiSchema<T[], S, F>;
796+
} else {
797+
// For fixed items, uiSchema.items can be an array, a function, or a single object
798+
if (Array.isArray(uiSchema.items)) {
799+
itemUiSchema = uiSchema.items[index] as UiSchema<T[], S, F>;
800+
} else {
801+
// Use the helper method for function or static object cases
802+
itemUiSchema = this.computeItemUiSchema(uiSchema, item, index, formContext);
803+
}
804+
}
759805
const itemErrorSchema = errorSchema ? (errorSchema[index] as ErrorSchema<T[]>) : undefined;
760806

761807
return this.renderArrayFieldItem({
@@ -811,7 +857,7 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
811857
canMoveDown: boolean;
812858
itemSchema: S;
813859
itemData: T[];
814-
itemUiSchema: UiSchema<T[], S, F>;
860+
itemUiSchema: UiSchema<T[], S, F> | undefined;
815861
itemIdSchema: IdSchema<T[]>;
816862
itemErrorSchema?: ErrorSchema<T[]>;
817863
autofocus?: boolean;

0 commit comments

Comments
 (0)