@rjsf/core 6.3.1 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rjsf/core",
3
- "version": "6.3.1",
3
+ "version": "6.4.0",
4
4
  "description": "A simple React component capable of building HTML forms out of a JSON schema.",
5
5
  "scripts": {
6
6
  "compileReplacer": "tsc -p tsconfig.replacer.json && move-file lodashReplacer.js lodashReplacer.cjs",
@@ -76,9 +76,9 @@
76
76
  "prop-types": "^15.8.1"
77
77
  },
78
78
  "devDependencies": {
79
- "@rjsf/snapshot-tests": "6.3.1",
80
- "@rjsf/utils": "6.3.1",
81
- "@rjsf/validator-ajv8": "6.3.1",
79
+ "@rjsf/snapshot-tests": "6.4.0",
80
+ "@rjsf/utils": "6.4.0",
81
+ "@rjsf/validator-ajv8": "6.4.0",
82
82
  "@testing-library/jest-dom": "^6.9.1",
83
83
  "@testing-library/react": "^16.3.2",
84
84
  "@testing-library/user-event": "^14.6.1",
@@ -308,6 +308,8 @@ export interface FormState<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
308
308
  initialDefaultsGenerated: boolean;
309
309
  /** The registry (re)computed only when props changed */
310
310
  registry: Registry<T, S, F>;
311
+ /** Tracks the previous `extraErrors` prop reference so that `getDerivedStateFromProps` can detect changes */
312
+ _prevExtraErrors?: ErrorSchema<T>;
311
313
  }
312
314
 
313
315
  /** The event data passed when changes have been made to the form, includes everything from the `FormState` except
@@ -374,6 +376,38 @@ export default class Form<
374
376
  */
375
377
  private _isProcessingUserChange = false;
376
378
 
379
+ /** When the `extraErrors` prop changes, re-merges `schemaValidationErrors` + `extraErrors` + `customErrors` into
380
+ * state before render, ensuring the updated errors are visible immediately in a single render cycle.
381
+ *
382
+ * @param props - The current props
383
+ * @param state - The current state
384
+ * @returns Partial state with re-merged errors if `extraErrors` changed, or `null` if no update is needed
385
+ */
386
+ static getDerivedStateFromProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
387
+ props: FormProps<T, S, F>,
388
+ state: FormState<T, S, F>,
389
+ ): Partial<FormState<T, S, F>> | null {
390
+ if (props.extraErrors !== state._prevExtraErrors) {
391
+ const baseErrors: ValidationData<T> = {
392
+ errors: state.schemaValidationErrors || [],
393
+ errorSchema: (state.schemaValidationErrorSchema || {}) as ErrorSchema<T>,
394
+ };
395
+ let { errors, errorSchema } = baseErrors;
396
+ if (props.extraErrors) {
397
+ ({ errors, errorSchema } = validationDataMerge<T>(baseErrors, props.extraErrors));
398
+ }
399
+ if (state.customErrors) {
400
+ ({ errors, errorSchema } = validationDataMerge<T>(
401
+ { errors, errorSchema },
402
+ state.customErrors.ErrorSchema,
403
+ true,
404
+ ));
405
+ }
406
+ return { _prevExtraErrors: props.extraErrors, errors, errorSchema };
407
+ }
408
+ return null;
409
+ }
410
+
377
411
  /** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
378
412
  * `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
379
413
  * state construction.
@@ -389,7 +423,10 @@ export default class Form<
389
423
 
390
424
  const { formData: propsFormData, initialFormData, onChange } = props;
391
425
  const formData = propsFormData ?? initialFormData;
392
- this.state = this.getStateFromProps(props, formData, undefined, undefined, undefined, true);
426
+ this.state = {
427
+ ...this.getStateFromProps(props, formData, undefined, undefined, undefined, true),
428
+ _prevExtraErrors: props.extraErrors,
429
+ };
393
430
  if (onChange && !deepEquals(this.state.formData, formData)) {
394
431
  onChange(toIChangeEvent(this.state));
395
432
  }
@@ -1219,17 +1256,12 @@ export default class Form<
1219
1256
  const { extraErrors, extraErrorsBlockSubmit, focusOnFirstError, onError } = this.props;
1220
1257
  const { errors: prevErrors } = this.state;
1221
1258
  const schemaValidation = this.validate(formData);
1222
- let errors = schemaValidation.errors;
1223
- let errorSchema = schemaValidation.errorSchema;
1224
- const schemaValidationErrors = errors;
1225
- const schemaValidationErrorSchema = errorSchema;
1226
- const hasError = errors.length > 0 || (extraErrors && extraErrorsBlockSubmit);
1259
+ // Always merge extraErrors so they remain visible in state regardless of extraErrorsBlockSubmit.
1260
+ const { errors, errorSchema } = extraErrors ? this.mergeErrors(schemaValidation, extraErrors) : schemaValidation;
1261
+ // hasError gates submission: schema errors always block; extraErrors only block when
1262
+ // extraErrorsBlockSubmit is set (non-breaking default: extraErrors are informational only).
1263
+ const hasError = schemaValidation.errors.length > 0 || (extraErrors && extraErrorsBlockSubmit);
1227
1264
  if (hasError) {
1228
- if (extraErrors) {
1229
- const merged = validationDataMerge(schemaValidation, extraErrors);
1230
- errorSchema = merged.errorSchema;
1231
- errors = merged.errors;
1232
- }
1233
1265
  if (focusOnFirstError) {
1234
1266
  if (typeof focusOnFirstError === 'function') {
1235
1267
  focusOnFirstError(errors[0]);
@@ -1241,8 +1273,8 @@ export default class Form<
1241
1273
  {
1242
1274
  errors,
1243
1275
  errorSchema,
1244
- schemaValidationErrors,
1245
- schemaValidationErrorSchema,
1276
+ schemaValidationErrors: schemaValidation.errors,
1277
+ schemaValidationErrorSchema: schemaValidation.errorSchema,
1246
1278
  },
1247
1279
  () => {
1248
1280
  if (onError) {
@@ -1252,6 +1284,14 @@ export default class Form<
1252
1284
  }
1253
1285
  },
1254
1286
  );
1287
+ } else if (errors.length > 0) {
1288
+ // Non-blocking extraErrors are present — update display state without triggering onError.
1289
+ this.setState({
1290
+ errors,
1291
+ errorSchema,
1292
+ schemaValidationErrors: [],
1293
+ schemaValidationErrorSchema: {},
1294
+ });
1255
1295
  } else if (prevErrors.length > 0) {
1256
1296
  this.setState({
1257
1297
  errors: [],
@@ -199,7 +199,8 @@ function ArrayAsMultiSelect<T = any, S extends StrictRJSFSchema = RJSFSchema, F
199
199
  } = props;
200
200
  const { widgets, schemaUtils, globalFormOptions, globalUiOptions } = registry;
201
201
  const itemsSchema = schemaUtils.retrieveSchema(schema.items as S, items);
202
- const enumOptions = optionsList<T[], S, F>(itemsSchema, uiSchema);
202
+ const itemsUiSchema = (uiSchema?.items ?? {}) as UiSchema<T[], S, F>;
203
+ const enumOptions = optionsList<T[], S, F>(itemsSchema, itemsUiSchema);
203
204
  const { widget = 'select', title: uiTitle, ...options } = getUiOptions<T[], S, F>(uiSchema, globalUiOptions);
204
205
  const Widget = getWidget<T[], S, F>(schema, widget, widgets);
205
206
  const label = uiTitle ?? schema.title ?? name;