Skip to content

Commit f5a24b2

Browse files
Bug: schema with conditions where switching to then/else subschemas did not reflect the actual validation errors in the onChange event. (#4432)
* Fixed issue with schema if/then/else conditions where switching to then/else subschemas did not reflect the actual validation errors in the onChange event. * improvement based on feedback --------- Co-authored-by: Heath C <[email protected]>
1 parent c2a60d4 commit f5a24b2

File tree

3 files changed

+77
-4
lines changed

3 files changed

+77
-4
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ should change the heading of the (upcoming) version to include a major version b
1616
1717
-->
1818

19-
# 5.23.3
19+
# 5.24.0
20+
21+
## @rjsf/core
22+
23+
- Fixed issue with schema if/then/else conditions where switching to then/else subschemas did not reflect the actual validation errors in the onChange event, fixing [#4249](https://github.com/rjsf-team/react-jsonschema-form/issues/4249) and improving performance.
2024

2125
## @rjsf/utils
2226

packages/core/src/components/Form.tsx

+22-3
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,9 @@ export default class Form<
416416
);
417417
}
418418
const formData: T = schemaUtils.getDefaultFormState(schema, inputFormData) as T;
419-
const _retrievedSchema = retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData);
419+
const _retrievedSchema = this.updateRetrievedSchema(
420+
retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData)
421+
);
420422

421423
const getCurrentErrors = (): ValidationData<T> => {
422424
// If the `props.noValidate` option is set or the schema has changed, we reset the error state.
@@ -459,6 +461,7 @@ export default class Form<
459461
errors = currentErrors.errors;
460462
errorSchema = currentErrors.errorSchema;
461463
}
464+
462465
if (props.extraErrors) {
463466
const merged = validationDataMerge({ errorSchema, errors }, props.extraErrors);
464467
errorSchema = merged.errorSchema;
@@ -649,11 +652,13 @@ export default class Form<
649652
*/
650653
onChange = (formData: T | undefined, newErrorSchema?: ErrorSchema<T>, id?: string) => {
651654
const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
652-
const { schemaUtils, schema, retrievedSchema } = this.state;
655+
const { schemaUtils, schema } = this.state;
653656

657+
let retrievedSchema = this.state.retrievedSchema;
654658
if (isObject(formData) || Array.isArray(formData)) {
655-
const newState = this.getStateFromProps(this.props, formData, retrievedSchema);
659+
const newState = this.getStateFromProps(this.props, formData);
656660
formData = newState.formData;
661+
retrievedSchema = newState.retrievedSchema;
657662
}
658663

659664
const mustValidate = !noValidate && liveValidate;
@@ -703,6 +708,20 @@ export default class Form<
703708
this.setState(state as FormState<T, S, F>, () => onChange && onChange({ ...this.state, ...state }, id));
704709
};
705710

711+
/**
712+
* If the retrievedSchema has changed the new retrievedSchema is returned.
713+
* Otherwise, the old retrievedSchema is returned to persist reference.
714+
* - This ensures that AJV retrieves the schema from the cache when it has not changed,
715+
* avoiding the performance cost of recompiling the schema.
716+
*
717+
* @param retrievedSchema The new retrieved schema.
718+
* @returns The new retrieved schema if it has changed, else the old retrieved schema.
719+
*/
720+
private updateRetrievedSchema(retrievedSchema: S) {
721+
const isTheSame = deepEquals(retrievedSchema, this.state?.retrievedSchema);
722+
return isTheSame ? this.state.retrievedSchema : retrievedSchema;
723+
}
724+
706725
/**
707726
* Callback function to handle reset form data.
708727
* - Reset all fields with default values.

packages/core/test/ObjectField.test.jsx

+50
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,56 @@ describe('ObjectField', () => {
227227
});
228228
});
229229

230+
it('Check schema with if/then/else conditions and activate the then/else subschemas, the onChange event should reflect the actual validation errors', () => {
231+
const schema = {
232+
type: 'object',
233+
_const: 'test',
234+
required: ['checkbox'],
235+
properties: {
236+
checkbox: {
237+
type: 'boolean',
238+
},
239+
},
240+
if: {
241+
required: ['checkbox'],
242+
properties: {
243+
checkbox: {
244+
const: true,
245+
},
246+
},
247+
},
248+
then: {
249+
required: ['text'],
250+
properties: {
251+
text: {
252+
type: 'string',
253+
},
254+
},
255+
},
256+
};
257+
258+
const { node, onChange } = createFormComponent({
259+
schema,
260+
formData: {
261+
checkbox: true,
262+
},
263+
liveValidate: true,
264+
});
265+
266+
// Uncheck the checkbox
267+
fireEvent.click(node.querySelector('input[type=checkbox]'));
268+
269+
sinon.assert.calledWithMatch(
270+
onChange.lastCall,
271+
{
272+
formData: { checkbox: false },
273+
errorSchema: {},
274+
errors: [],
275+
},
276+
'root_checkbox'
277+
);
278+
});
279+
230280
it('Check that when formData changes, the form should re-validate', () => {
231281
const { node, rerender } = createFormComponent({
232282
schema,

0 commit comments

Comments
 (0)