Skip to content
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

Skip populate function proposal in Experimental_ArrayMinItems #4121

Merged
merged 8 commits into from
Mar 21, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ should change the heading of the (upcoming) version to include a major version b
## @rjsf/utils

- Added a new `skipEmptyDefault` option in `emptyObjectFields`, fixing [#3880](https://github.com/rjsf-team/react-jsonschema-form/issues/3880)
- Added a new `computeSkipPopulate` option in `arrayMinItems`, allowing custom logic to skip populating arrays with default values, implementing [#4121](https://github.com/rjsf-team/react-jsonschema-form/pull/4121).
- Fixed bug where the string `"\</strong>"` would get printed next to filenames when uploading files, and restored intended bolding of filenames fixing [#4120](https://github.com/rjsf-team/react-jsonschema-form/issues/4120).

## Dev / docs / playground
Expand Down
64 changes: 64 additions & 0 deletions packages/docs/docs/api-reference/form-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,70 @@ Optional enumerated flag controlling how array minItems are populated, defaultin
| `requiredOnly` | Ignore `minItems` on a field when calculating defaults unless the field is required. |
| `never` | Ignore `minItems` on a field when calculating defaults for required and non-required. Value will set only if defined `default` and from `formData` |

#### `arrayMinItems.computeSkipPopulate`

The signature and documentation for this property is as follow:

##### computeSkipPopulate <T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>()

A function that determines whether to skip populating the array with default values based on the provided validator, schema, and root schema.
If the function returns `true`, the array will not be populated with default values.
If the function returns `false`, the array will be populated with default values according to the `populate` option.

###### Parameters

- validator: ValidatorType<T, S, F> - An implementation of the `ValidatorType` interface that is used to detect valid schema conditions
- schema: S - The schema for which resolving a condition is desired
- [rootSchema]: S - The root schema that will be forwarded to all the APIs

###### Returns

- boolean: A boolean indicating whether to skip populating the array with default values.


##### Example

MarekBodingerBA marked this conversation as resolved.
Show resolved Hide resolved
```tsx
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';

const schema: RJSFSchema = {
type: 'object',
properties: {
stringArray: {
type: 'array',
items: { type: 'string' },
minItems: 1,
},
numberArray: {
type: 'array',
items: { type: 'number' },
minItems: 1,
},
},
required: ['stringArray', 'numberArray'],
};

const computeSkipPopulateNumberArrays = (validator, schema, rootSchema) =>
// These conditions are needed to narrow down the type of the schema.items
!Array.isArray(schema?.items) &&
typeof schema?.items !== 'boolean' &&
schema?.items?.type === 'number',

render(
<Form
schema={schema}
validator={validator}
experimental_defaultFormStateBehavior={{
arrayMinItems: {
computeSkipPopulate: computeSkipPopulateNumberArrays,
},
}}
/>,
document.getElementById('app')
);
```

#### `arrayMinItems.mergeExtraDefaults`

Optional boolean flag, defaulting to `false` when not specified.
Expand Down
3 changes: 3 additions & 0 deletions packages/utils/src/schema/getDefaultFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
const neverPopulate = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'never';
const ignoreMinItemsFlagSet = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'requiredOnly';
const isSkipEmptyDefaults = experimental_defaultFormStateBehavior?.emptyObjectFields === 'skipEmptyDefaults';
const computeSkipPopulate =
experimental_defaultFormStateBehavior?.arrayMinItems?.computeSkipPopulate ?? (() => false);

const emptyDefault = isSkipEmptyDefaults ? undefined : [];

Expand Down Expand Up @@ -399,6 +401,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
if (
!schema.minItems ||
isMultiSelect<T, S, F>(validator, schema, rootSchema) ||
computeSkipPopulate<T, S, F>(validator, schema, rootSchema) ||
schema.minItems <= defaultsLength
) {
return defaults ? defaults : emptyDefault;
Expand Down
14 changes: 14 additions & 0 deletions packages/utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ export type Experimental_ArrayMinItems = {
* - `never`: Ignore `minItems` on a field even the field is required.
*/
populate?: 'all' | 'requiredOnly' | 'never';
/** A function that determines whether to skip populating the array with default values based on the provided validator,
* schema, and root schema.
* If the function returns true, the array will not be populated with default values.
* If the function returns false, the array will be populated with default values according to the `populate` option.
* @param validator - An implementation of the `ValidatorType` interface that is used to detect valid schema conditions
* @param schema - The schema for which resolving a condition is desired
* @param [rootSchema] - The root schema that will be forwarded to all the APIs
* @returns A boolean indicating whether to skip populating the array with default values.
*/
computeSkipPopulate?: <T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
validator: ValidatorType<T, S, F>,
schema: S,
rootSchema?: S
) => boolean;
/** When `formData` is provided and does not contain `minItems` worth of data, this flag (`false` by default) controls
* whether the extra data provided by the defaults is appended onto the existing `formData` items to ensure the
* `minItems` condition is met. When false (legacy behavior), only the `formData` provided is merged into the default
Expand Down
26 changes: 26 additions & 0 deletions packages/utils/test/schema/getDefaultFormStateTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2872,5 +2872,31 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType
})
).toEqual({ requiredArray: ['raw0', 'default0'] });
});
it('should not populate defaults for array items when computeSkipPopulate returns true', () => {
const schema: RJSFSchema = {
type: 'object',
properties: {
stringArray: {
type: 'array',
items: { type: 'string' },
minItems: 1,
},
numberArray: {
type: 'array',
items: { type: 'number' },
minItems: 1,
},
},
required: ['stringArray', 'numberArray'],
};
expect(
getDefaultFormState(testValidator, schema, {}, schema, false, {
arrayMinItems: {
computeSkipPopulate: (_, schema) =>
!Array.isArray(schema?.items) && typeof schema?.items !== 'boolean' && schema?.items?.type === 'number',
},
})
).toEqual({ stringArray: [undefined], numberArray: [] });
});
});
}