Skip to content

Commit e74cc88

Browse files
authored
Bug: Custom validate errors are not cleared when the form is valid (rjsf-team#4499)
* continue * Fixed issue with customValidate errors not being cleared. * improvement based on feedback * fixed issue with error only shown in the errors list and not in errorSchema field __errors array. * Created test case * updated changelog
1 parent 1238e0a commit e74cc88

File tree

3 files changed

+125
-4
lines changed

3 files changed

+125
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ 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+
# 5.24.4
19+
20+
## @rjsf/utils
21+
22+
- fixed issue with customValidate errors are not cleared when the form is valid [4365](https://github.com/rjsf-team/react-jsonschema-form/pull/4365) due to regression
23+
1824
# 5.24.3
1925

2026
## @rjsf/utils

packages/core/src/components/Form.tsx

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
ValidatorType,
3636
Experimental_DefaultFormStateBehavior,
3737
Experimental_CustomMergeAllOf,
38+
createErrorHandler,
39+
unwrapErrorHandler,
3840
} from '@rjsf/utils';
3941
import _forEach from 'lodash/forEach';
4042
import _get from 'lodash/get';
@@ -519,6 +521,22 @@ export default class Form<
519521
return shouldRender(this, nextProps, nextState);
520522
}
521523

524+
/** Gets the previously raised customValidate errors.
525+
*
526+
* @returns the previous customValidate errors
527+
*/
528+
private getPreviousCustomValidateErrors(): ErrorSchema<T> {
529+
const { customValidate, uiSchema } = this.props;
530+
const prevFormData = this.state.formData as T;
531+
let customValidateErrors = {};
532+
if (typeof customValidate === 'function') {
533+
const errorHandler = customValidate(prevFormData, createErrorHandler<T>(prevFormData), uiSchema);
534+
const userErrorSchema = unwrapErrorHandler<T>(errorHandler);
535+
customValidateErrors = userErrorSchema;
536+
}
537+
return customValidateErrors;
538+
}
539+
522540
/** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the
523541
* `schemaUtils` in the state), returning the results.
524542
*
@@ -644,18 +662,39 @@ export default class Form<
644662
if (resolvedSchema?.type !== 'object' && resolvedSchema?.type !== 'array') {
645663
filteredErrors.__errors = schemaErrors.__errors;
646664
}
665+
666+
const prevCustomValidateErrors = this.getPreviousCustomValidateErrors();
667+
// Filtering out the previous raised customValidate errors so that they are cleared when no longer valid.
668+
const filterPreviousCustomErrors = (errors: string[] = [], prevCustomErrors: string[]) => {
669+
if (errors.length === 0) {
670+
return errors;
671+
}
672+
673+
return errors.filter((error) => {
674+
return !prevCustomErrors.includes(error);
675+
});
676+
};
677+
647678
// Removing undefined, null and empty errors.
648-
const filterNilOrEmptyErrors = (errors: any): ErrorSchema<T> => {
679+
const filterNilOrEmptyErrors = (errors: any, previousCustomValidateErrors: any = {}): ErrorSchema<T> => {
649680
_forEach(errors, (errorAtKey, errorKey: keyof typeof errors) => {
650-
if (_isNil(errorAtKey)) {
681+
const prevCustomValidateErrorAtKey = previousCustomValidateErrors[errorKey];
682+
if (_isNil(errorAtKey) || (Array.isArray(errorAtKey) && errorAtKey.length === 0)) {
651683
delete errors[errorKey];
684+
} else if (
685+
isObject(errorAtKey) &&
686+
isObject(prevCustomValidateErrorAtKey) &&
687+
Array.isArray(prevCustomValidateErrorAtKey?.__errors)
688+
) {
689+
// if previous customValidate error is an object and has __errors array, filter out the errors previous customValidate errors.
690+
errors[errorKey] = filterPreviousCustomErrors(errorAtKey.__errors, prevCustomValidateErrorAtKey.__errors);
652691
} else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) {
653-
filterNilOrEmptyErrors(errorAtKey);
692+
filterNilOrEmptyErrors(errorAtKey, previousCustomValidateErrors[errorKey]);
654693
}
655694
});
656695
return errors;
657696
};
658-
return filterNilOrEmptyErrors(filteredErrors);
697+
return filterNilOrEmptyErrors(filteredErrors, prevCustomValidateErrors);
659698
}
660699

661700
/** Function to handle changes made to a field in the `Form`. This handler receives an entirely new copy of the

packages/core/test/Form.test.jsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2671,6 +2671,82 @@ describeRepeated('Form common', (createFormComponent) => {
26712671
);
26722672
});
26732673
});
2674+
2675+
describe('customValidate errors', () => {
2676+
it('customValidate should raise an error when End is larger than Start field.', () => {
2677+
let schema = {
2678+
required: ['Start', 'End'],
2679+
properties: {
2680+
Start: {
2681+
type: 'number',
2682+
},
2683+
End: {
2684+
type: 'number',
2685+
},
2686+
},
2687+
type: 'object',
2688+
};
2689+
2690+
// customValidate method to raise an error when Start is larger than End field.
2691+
const customValidate = (formData, errors) => {
2692+
if (formData['Start'] > formData['End']) {
2693+
errors['Start']?.addError('Validate error: Test should be LE than End');
2694+
}
2695+
return errors;
2696+
};
2697+
2698+
const { node, onChange } = createFormComponent({
2699+
schema,
2700+
liveValidate: true,
2701+
formData: { Start: 2, End: 1 },
2702+
customValidate,
2703+
});
2704+
2705+
expect(node.querySelectorAll('#root_Start__error')).to.have.length(1);
2706+
let errorMessageContent = node.querySelector('#root_Start__error .text-danger').textContent;
2707+
expect(errorMessageContent).to.contain('Validate error: Test should be LE than End');
2708+
2709+
// Change the End field to a larger value than Start field to remove customValidate raised errors.
2710+
const endInput = node.querySelector('#root_End');
2711+
act(() => {
2712+
fireEvent.change(endInput, {
2713+
target: { value: 3 },
2714+
});
2715+
});
2716+
2717+
expect(node.querySelectorAll('#root_Start__error')).to.have.length(0);
2718+
sinon.assert.calledWithMatch(
2719+
onChange.lastCall,
2720+
{
2721+
errorSchema: {},
2722+
errors: [],
2723+
},
2724+
'root'
2725+
);
2726+
2727+
// Change the End field to a lesser value than Start field to raise customValidate errors.
2728+
act(() => {
2729+
fireEvent.change(endInput, {
2730+
target: { value: 0 },
2731+
});
2732+
});
2733+
2734+
expect(node.querySelectorAll('#root_Start__error')).to.have.length(1);
2735+
errorMessageContent = node.querySelector('#root_Start__error .text-danger').textContent;
2736+
expect(errorMessageContent).to.contain('Validate error: Test should be LE than End');
2737+
sinon.assert.calledWithMatch(
2738+
onChange.lastCall,
2739+
{
2740+
errorSchema: {
2741+
Start: {
2742+
__errors: ['Validate error: Test should be LE than End'],
2743+
},
2744+
},
2745+
},
2746+
'root'
2747+
);
2748+
});
2749+
});
26742750
});
26752751

26762752
describe('Schema and formData updates', () => {

0 commit comments

Comments
 (0)