Skip to content

Commit 8d4b1a0

Browse files
Merge branch 'main' into fix-4707
2 parents 344196e + eb77ce9 commit 8d4b1a0

34 files changed

+656
-297
lines changed

.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"beforeSelfClosing": "always"
2020
}
2121
],
22+
"react-hooks/exhaustive-deps": "error",
2223
"curly": [
2324
2
2425
],

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ should change the heading of the (upcoming) version to include a major version b
2121

2222
- 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))
2323
- 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)
24+
- Updated all of the `XxxxField` components and `Form` to handle the new `path` parameter in `FieldProps.onChange`, making `Form` queue up changes so that they are all processed and no data is lost, fixing [#3367](https://github.com/rjsf-team/react-jsonschema-form/issues/3367)
25+
- Updated a bug in `AltDateWidget` related to the `clear` button not working after the fix for [#3367](https://github.com/rjsf-team/react-jsonschema-form/issues/3367)
26+
- Fixed the missing hook dependencies for the `CheckboxesWidget` so that they work properly
2427

2528
## @rjsf/chakra-ui
2629

@@ -30,6 +33,7 @@ should change the heading of the (upcoming) version to include a major version b
3033

3134
- 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)
3235
- Fixed additional properties rendering by properly connecting the `FieldTemplate` and `WrapIfAdditionalTemplate`, fixing [4707](https://github.com/rjsf-team/react-jsonschema-form/issues/4707)
36+
- Fixed the missing hook dependencies in the `DateTimeWidget` and `DateWidget` so that they work properly
3337

3438
## @rjsf/fluentui-rc
3539

@@ -63,6 +67,7 @@ should change the heading of the (upcoming) version to include a major version b
6367

6468
- 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))
6569
- Added `title` property to `RJSFValidationError` [PR](https://github.com/rjsf-team/react-jsonschema-form/pull/4700)
70+
- BREAKING CHANGE: Updated the `FieldProps` interface's `onChange` handler to inject a new optional `path` before the `ErrorSchema` parameter as part of the fix for [#3367](https://github.com/rjsf-team/react-jsonschema-form/issues/3367)
6671

6772
## @rjsf/validator-ajv8
6873

@@ -73,6 +78,9 @@ should change the heading of the (upcoming) version to include a major version b
7378
- Added comprehensive documentation for dynamic UI schema feature with TypeScript examples ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
7479
- Updated array documentation to reference the new dynamic UI schema capabilities ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
7580
- Updated nearly all of the libraries in the `package.json` files to the latest non-breaking versions
81+
- Fixed the broken `Custom Array` sample
82+
- Improved the `Any Of with Custom Field` sample so that it renders using the appropriate theme components
83+
- Updated the `custom-widgets-fields.md` and `v6.x upgrade guide.md` to document the BREAKING CHANGE to the `FieldProps.onChange` behavior
7684

7785
# 6.0.0-beta.13
7886

docs/index.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
# react-jsonschema-form
22

33
The react-jsonschema-form docs have been moved [here](https://rjsf-team.github.io/react-jsonschema-form/docs).
4-
5-
We are in the process of migrating our versioned documentation. For documentation prior to version 5.0.0, please select the version in the bottom-right corner of this page.

package-lock.json

Lines changed: 37 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"build-serial": "nx run-many --target=build --parallel=1",
1717
"start": "echo 'use \"npm run build\" from main directory and then \"npm start\" in the playground package'",
1818
"pre-commit:husky": "nx run-many --parallel=1 --target=precommit",
19-
"prepare": "husky install",
19+
"prepare": "is-ci || husky",
2020
"format": "prettier --write .",
2121
"format-check": "prettier --check .",
2222
"bump-all-packages": "echo 'NOTE: Make sure to sanity check the playground locally before commiting changes' && npm update --save && npm install && npm run lint && npm run build && npm run test",
@@ -69,6 +69,7 @@
6969
"eslint-plugin-react": "^7.37.5",
7070
"eslint-plugin-react-hooks": "^5.2.0",
7171
"husky": "^9.1.7",
72+
"is-ci": "^4.1.0",
7273
"jest": "^30.0.5",
7374
"jest-environment-jsdom": "^30.0.5",
7475
"jest-watch-typeahead": "^3.0.1",

packages/core/src/components/Form.tsx

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ import {
3838
createErrorHandler,
3939
unwrapErrorHandler,
4040
} from '@rjsf/utils';
41+
import _cloneDeep from 'lodash/cloneDeep';
4142
import _forEach from 'lodash/forEach';
4243
import _get from 'lodash/get';
4344
import _isEmpty from 'lodash/isEmpty';
4445
import _isNil from 'lodash/isNil';
4546
import _pick from 'lodash/pick';
47+
import _set from 'lodash/set';
4648
import _toPath from 'lodash/toPath';
4749

4850
import getDefaultRegistry from '../getDefaultRegistry';
@@ -272,6 +274,19 @@ export interface IChangeEvent<T = any, S extends StrictRJSFSchema = RJSFSchema,
272274
status?: 'submitted';
273275
}
274276

277+
/** The definition of a pending change that will be processed in the `onChange` handler
278+
*/
279+
interface PendingChange<T> {
280+
/** The path into the formData/errorSchema at which the `newValue`/`newErrorSchema` will be set */
281+
path?: (number | string)[];
282+
/** The new value to set into the formData */
283+
newValue?: T;
284+
/** The new errors to be set into the errorSchema, if any */
285+
newErrorSchema?: ErrorSchema<T>;
286+
/** The optional id of the field for which the change is being made */
287+
id?: string;
288+
}
289+
275290
/** The `Form` component renders the outer form and all the fields defined in the `schema` */
276291
export default class Form<
277292
T = any,
@@ -283,6 +298,10 @@ export default class Form<
283298
*/
284299
formElement: RefObject<any>;
285300

301+
/** The list of pending changes
302+
*/
303+
pendingChanges: PendingChange<T>[] = [];
304+
286305
/** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
287306
* `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
288307
* state construction.
@@ -539,8 +558,7 @@ export default class Form<
539558
let customValidateErrors = {};
540559
if (typeof customValidate === 'function') {
541560
const errorHandler = customValidate(prevFormData, createErrorHandler<T>(prevFormData), uiSchema);
542-
const userErrorSchema = unwrapErrorHandler<T>(errorHandler);
543-
customValidateErrors = userErrorSchema;
561+
customValidateErrors = unwrapErrorHandler<T>(errorHandler);
544562
}
545563
return customValidateErrors;
546564
}
@@ -550,7 +568,8 @@ export default class Form<
550568
*
551569
* @param formData - The new form data to validate
552570
* @param schema - The schema used to validate against
553-
* @param altSchemaUtils - The alternate schemaUtils to use for validation
571+
* @param [altSchemaUtils] - The alternate schemaUtils to use for validation
572+
* @param [retrievedSchema] - An optionally retrieved schema for per
554573
*/
555574
validate(
556575
formData: T | undefined,
@@ -655,11 +674,16 @@ export default class Form<
655674
const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
656675
const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData);
657676
const fieldNames = this.getFieldNames(pathSchema, formData);
658-
const newFormData = this.getUsedFormData(formData, fieldNames);
659-
return newFormData;
677+
return this.getUsedFormData(formData, fieldNames);
660678
};
661679

662-
// Filtering errors based on your retrieved schema to only show errors for properties in the selected branch.
680+
/** Filtering errors based on your retrieved schema to only show errors for properties in the selected branch.
681+
*
682+
* @param schemaErrors - The schema errors to filter
683+
* @param [resolvedSchema] - An optionally resolved schema to use for performance reasons
684+
* @param [formData] - The formData to help filter errors
685+
* @private
686+
*/
663687
private filterErrorsBasedOnSchema(schemaErrors: ErrorSchema<T>, resolvedSchema?: S, formData?: any): ErrorSchema<T> {
664688
const { retrievedSchema, schemaUtils } = this.state;
665689
const _retrievedSchema = resolvedSchema ?? retrievedSchema;
@@ -705,23 +729,47 @@ export default class Form<
705729
return filterNilOrEmptyErrors(filteredErrors, prevCustomValidateErrors);
706730
}
707731

708-
/** Function to handle changes made to a field in the `Form`. This handler receives an entirely new copy of the
709-
* `formData` along with a new `ErrorSchema`. It will first update the `formData` with any missing default fields and
710-
* then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not
711-
* in a form field. Then, the resulting formData will be validated if required. The state will be updated with the new
712-
* updated (potentially filtered) `formData`, any errors that resulted from validation. Finally the `onChange`
713-
* callback will be called if specified with the updated state.
732+
/** Pushes the given change information into the `pendingChanges` array and then calls `processPendingChanges()` if
733+
* the array only contains a single pending change.
714734
*
715-
* @param formData - The new form data from a change to a field
716-
* @param newErrorSchema - The new `ErrorSchema` based on the field change
717-
* @param id - The id of the field that caused the change
735+
* @param newValue - The new form data from a change to a field
736+
* @param [path] - The path to the change into which to set the formData
737+
* @param [newErrorSchema] - The new `ErrorSchema` based on the field change
738+
* @param [id] - The id of the field that caused the change
739+
*/
740+
onChange = (newValue: T | undefined, path?: (number | string)[], newErrorSchema?: ErrorSchema<T>, id?: string) => {
741+
this.pendingChanges.push({ newValue, path, newErrorSchema, id });
742+
if (this.pendingChanges.length === 1) {
743+
this.processPendingChange();
744+
}
745+
};
746+
747+
/** Function to handle changes made to a field in the `Form`. This handler gets the first change from the
748+
* `pendingChanges` list, containing the `newValue` for the `formData` and the `path` at which the `newValue` is to be
749+
* updated, along with a new, optional `ErrorSchema` for that same `path` and potentially the `id` of the field being
750+
* changed. It will first update the `formData` with any missing default fields and then, if `omitExtraData` and
751+
* `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not in a form field. Then, the
752+
* resulting `formData` will be validated if required. The state will be updated with the new updated (potentially
753+
* filtered) `formData`, any errors that resulted from validation. Finally the `onChange` callback will be called, if
754+
* specified, with the updated state and the `processPendingChange()` function is called again.
718755
*/
719-
onChange = (formData: T | undefined, newErrorSchema?: ErrorSchema<T>, id?: string) => {
720-
const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
721-
const { schemaUtils, schema } = this.state;
756+
processPendingChange() {
757+
if (this.pendingChanges.length === 0) {
758+
return;
759+
}
760+
const { newValue, path, id } = this.pendingChanges[0];
761+
let { newErrorSchema } = this.pendingChanges[0];
762+
const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange, idPrefix = '' } = this.props;
763+
const { formData: oldFormData, schemaUtils, schema, errorSchema } = this.state;
722764

765+
const isRootPath = !path || path.length === 0 || (path.length === 1 && path[0] === idPrefix);
723766
let retrievedSchema = this.state.retrievedSchema;
767+
let formData = isRootPath ? newValue : _cloneDeep(oldFormData);
724768
if (isObject(formData) || Array.isArray(formData)) {
769+
if (!isRootPath) {
770+
// If the newValue is not on the root path, then set it into the form data
771+
_set(formData, path, newValue);
772+
}
725773
const newState = this.getStateFromProps(this.props, formData);
726774
formData = newState.formData;
727775
retrievedSchema = newState.retrievedSchema;
@@ -738,6 +786,13 @@ export default class Form<
738786
};
739787
}
740788

789+
// First update the value in the newErrorSchema in a copy of the old error schema if it was specified and the path
790+
// is not the root
791+
if (newErrorSchema && !isRootPath) {
792+
const errorSchemaCopy = _cloneDeep(errorSchema);
793+
_set(errorSchemaCopy, path, newErrorSchema);
794+
newErrorSchema = errorSchemaCopy;
795+
}
741796
if (mustValidate) {
742797
const schemaValidation = this.validate(newFormData, schema, schemaUtils, retrievedSchema);
743798
let errors = schemaValidation.errors;
@@ -762,6 +817,7 @@ export default class Form<
762817
schemaValidationErrorSchema,
763818
};
764819
} else if (!noValidate && newErrorSchema) {
820+
// Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors.
765821
const errorSchema = extraErrors
766822
? (mergeObjects(newErrorSchema, extraErrors, 'preventDuplicates') as ErrorSchema<T>)
767823
: newErrorSchema;
@@ -771,8 +827,15 @@ export default class Form<
771827
errors: toErrorList(errorSchema),
772828
};
773829
}
774-
this.setState(state as FormState<T, S, F>, () => onChange && onChange({ ...this.state, ...state }, id));
775-
};
830+
this.setState(state as FormState<T, S, F>, () => {
831+
if (onChange) {
832+
onChange({ ...this.state, ...state }, id);
833+
}
834+
// Now remove the change we just completed and call this again
835+
this.pendingChanges.shift();
836+
this.processPendingChange();
837+
});
838+
}
776839

777840
/**
778841
* If the retrievedSchema has changed the new retrievedSchema is returned.
@@ -1029,7 +1092,7 @@ export default class Form<
10291092
const {
10301093
children,
10311094
id,
1032-
idPrefix,
1095+
idPrefix = '',
10331096
idSeparator,
10341097
className = '',
10351098
tagName,
@@ -1082,7 +1145,7 @@ export default class Form<
10821145
>
10831146
{showErrorList === 'top' && this.renderErrors(registry)}
10841147
<_SchemaField
1085-
name=''
1148+
name={idPrefix}
10861149
schema={schema}
10871150
uiSchema={uiSchema}
10881151
errorSchema={errorSchema}

0 commit comments

Comments
 (0)