Skip to content

Commit 57cad7d

Browse files
Fix 4782 to support proper live validation (#4785)
* Fix 4782 to support proper live validation Fixed #4782 by fixing `getStateFromProps()` around the new `skipLiveValidate` optimization flag - Updated `Form` to skip clearing the `errorSchema` for a field that was changed when the `mustValidate` flag is true - Updated the `mustValidate` flag to remove the `!skipLiveValidate` from it's assignment, moving it to the live validation branch instead - Updated `Playground` to make the `onChange()` handler separate the event from the destructure to assist in better debugging - Updated the `CHANGELOG.md` file accordingly * - Updated `filterErrorsBasedOnSchema()` to properly merge errors when `customValidate` is provided * - Fixed 4784 by allowing non-empty objects for leaf-level fields
1 parent 3a1f467 commit 57cad7d

File tree

3 files changed

+44
-19
lines changed

3 files changed

+44
-19
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ 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+
# 6.0.0-beta.19
19+
20+
## @rjsf/core
21+
22+
- Updated `Form` to fix live validation in `getStateFromProps()` broken by an optimization, fixing [#4782](https://github.com/rjsf-team/react-jsonschema-form/issues/4782)
23+
- Updated `Form` to fix error messages being displayed abnormally when `customValidate` is provided, fixing [#4783](https://github.com/rjsf-team/react-jsonschema-form/issues/4783)
24+
- Updated `Form` to fix `omitExtraData` when the leaf node happens to have an object value, fixing [#4784](https://github.com/rjsf-team/react-jsonschema-form/issues/4784)
25+
26+
## @rjsf/utils
27+
28+
- Updated `resolveSchema()` to pass the `experimental_customMergeAllOf` options properly to `resolveReference()` and `resolveDependencies()` called within it
29+
1830
# 6.0.0-beta.18
1931

2032
## @rjsf/chakra-ui

packages/core/src/components/Form.tsx

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
createSchemaUtils,
44
CustomValidator,
55
deepEquals,
6+
ERRORS_KEY,
67
ErrorSchema,
78
ErrorTransformer,
89
FormContextType,
@@ -416,6 +417,7 @@ export default class Form<
416417
* @param retrievedSchema - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`.
417418
* @param isSchemaChanged - A flag indicating whether the schema has changed.
418419
* @param formDataChangedFields - The changed fields of `formData`
420+
* @param skipLiveValidate - Optional flag, if true, means that we are not running live validation
419421
* @returns - The new state for the `Form`
420422
*/
421423
getStateFromProps(
@@ -432,7 +434,7 @@ export default class Form<
432434
const uiSchema: UiSchema<T, S, F> = ('uiSchema' in props ? props.uiSchema! : this.props.uiSchema!) || {};
433435
const edit = typeof inputFormData !== 'undefined';
434436
const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate;
435-
const mustValidate = edit && !props.noValidate && liveValidate && !skipLiveValidate;
437+
const mustValidate = edit && !props.noValidate && liveValidate;
436438
const experimental_defaultFormStateBehavior =
437439
'experimental_defaultFormStateBehavior' in props
438440
? props.experimental_defaultFormStateBehavior
@@ -484,7 +486,8 @@ export default class Form<
484486
let errorSchema: ErrorSchema<T> | undefined;
485487
let schemaValidationErrors: RJSFValidationError[] = state.schemaValidationErrors;
486488
let schemaValidationErrorSchema: ErrorSchema<T> = state.schemaValidationErrorSchema;
487-
if (mustValidate) {
489+
// If we are skipping live validate, it means that the state has already been updated with live validation errors
490+
if (mustValidate && !skipLiveValidate) {
488491
const schemaValidation = this.validate(formData, rootSchema, schemaUtils, _retrievedSchema);
489492
errors = schemaValidation.errors;
490493
// If retrievedSchema is undefined which means the schema or formData has changed, we do not merge state.
@@ -504,7 +507,8 @@ export default class Form<
504507
const currentErrors = getCurrentErrors();
505508
errors = currentErrors.errors;
506509
errorSchema = currentErrors.errorSchema;
507-
if (formDataChangedFields.length > 0) {
510+
// We only update the error schema for changed fields if mustValidate is false
511+
if (formDataChangedFields.length > 0 && !mustValidate) {
508512
const newErrorSchema = formDataChangedFields.reduce(
509513
(acc, key) => {
510514
acc[key] = undefined;
@@ -641,25 +645,28 @@ export default class Form<
641645
* @param [formData] - The form data to use while checking for empty objects/arrays
642646
*/
643647
getFieldNames = (pathSchema: PathSchema<T>, formData?: T): string[][] => {
648+
const formValueHasData = (value: T, isLeaf: boolean) =>
649+
typeof value !== 'object' || _isEmpty(value) || (isLeaf && !_isEmpty(value));
644650
const getAllPaths = (_obj: GenericObjectType, acc: string[][] = [], paths: string[][] = [[]]) => {
645-
Object.keys(_obj).forEach((key: string) => {
646-
if (typeof _obj[key] === 'object') {
651+
const objKeys = Object.keys(_obj);
652+
objKeys.forEach((key: string) => {
653+
const data = _obj[key];
654+
if (typeof data === 'object') {
647655
const newPaths = paths.map((path) => [...path, key]);
648656
// If an object is marked with additionalProperties, all its keys are valid
649-
if (_obj[key][RJSF_ADDITIONAL_PROPERTIES_FLAG] && _obj[key][NAME_KEY] !== '') {
650-
acc.push(_obj[key][NAME_KEY]);
657+
if (data[RJSF_ADDITIONAL_PROPERTIES_FLAG] && data[NAME_KEY] !== '') {
658+
acc.push(data[NAME_KEY]);
651659
} else {
652-
getAllPaths(_obj[key], acc, newPaths);
660+
getAllPaths(data, acc, newPaths);
653661
}
654-
} else if (key === NAME_KEY && _obj[key] !== '') {
662+
} else if (key === NAME_KEY && data !== '') {
655663
paths.forEach((path) => {
656664
const formValue = _get(formData, path);
657-
// adds path to fieldNames if it points to a value
658-
// or an empty object/array
665+
const isLeaf = objKeys.length === 1;
666+
// adds path to fieldNames if it points to a value or an empty object/array which is not a leaf
659667
if (
660-
typeof formValue !== 'object' ||
661-
_isEmpty(formValue) ||
662-
(Array.isArray(formValue) && formValue.every((val) => typeof val !== 'object'))
668+
formValueHasData(formValue, isLeaf) ||
669+
(Array.isArray(formValue) && formValue.every((val) => formValueHasData(val, isLeaf)))
663670
) {
664671
acc.push(path);
665672
}
@@ -700,7 +707,7 @@ export default class Form<
700707
const filteredErrors: ErrorSchema<T> = _pick(schemaErrors, fieldNames as unknown as string[]);
701708
// If the root schema is of a primitive type, do not filter out the __errors
702709
if (resolvedSchema?.type !== 'object' && resolvedSchema?.type !== 'array') {
703-
filteredErrors.__errors = schemaErrors.__errors;
710+
filteredErrors[ERRORS_KEY] = schemaErrors[ERRORS_KEY];
704711
}
705712

706713
const prevCustomValidateErrors = this.getPreviousCustomValidateErrors();
@@ -724,11 +731,16 @@ export default class Form<
724731
} else if (
725732
isObject(errorAtKey) &&
726733
isObject(prevCustomValidateErrorAtKey) &&
727-
Array.isArray(prevCustomValidateErrorAtKey?.__errors)
734+
Array.isArray(prevCustomValidateErrorAtKey?.[ERRORS_KEY])
728735
) {
729736
// if previous customValidate error is an object and has __errors array, filter out the errors previous customValidate errors.
730-
errors[errorKey] = filterPreviousCustomErrors(errorAtKey.__errors, prevCustomValidateErrorAtKey.__errors);
731-
} else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) {
737+
errors[errorKey] = {
738+
[ERRORS_KEY]: filterPreviousCustomErrors(
739+
errorAtKey[ERRORS_KEY],
740+
prevCustomValidateErrorAtKey?.[ERRORS_KEY],
741+
),
742+
};
743+
} else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey[ERRORS_KEY])) {
732744
filterNilOrEmptyErrors(errorAtKey, previousCustomValidateErrors[errorKey]);
733745
}
734746
});

packages/playground/src/components/Playground.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ export default function Playground({ themes, validators }: PlaygroundProps) {
149149
}, [onThemeSelected, load, loaded, setShowForm, theme, themes]);
150150

151151
const onFormDataChange = useCallback(
152-
({ formData }: IChangeEvent, id?: string) => {
152+
(event: IChangeEvent, id?: string) => {
153+
const { formData } = event;
153154
if (id) {
154155
console.log('Field changed, id: ', id);
155156
}

0 commit comments

Comments
 (0)