Skip to content

Bug: schema with conditions where switching to then/else subschemas did not reflect the actual validation errors in the onChange event. #4432

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ should change the heading of the (upcoming) version to include a major version b

-->

# 5.23.3
# 5.24.0

## @rjsf/core

- 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.

## @rjsf/utils

Expand Down
25 changes: 22 additions & 3 deletions packages/core/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,9 @@ export default class Form<
);
}
const formData: T = schemaUtils.getDefaultFormState(schema, inputFormData) as T;
const _retrievedSchema = retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData);
const _retrievedSchema = this.updateRetrievedSchema(
retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData)
);

const getCurrentErrors = (): ValidationData<T> => {
// If the `props.noValidate` option is set or the schema has changed, we reset the error state.
Expand Down Expand Up @@ -459,6 +461,7 @@ export default class Form<
errors = currentErrors.errors;
errorSchema = currentErrors.errorSchema;
}

if (props.extraErrors) {
const merged = validationDataMerge({ errorSchema, errors }, props.extraErrors);
errorSchema = merged.errorSchema;
Expand Down Expand Up @@ -649,11 +652,13 @@ export default class Form<
*/
onChange = (formData: T | undefined, newErrorSchema?: ErrorSchema<T>, id?: string) => {
const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
const { schemaUtils, schema, retrievedSchema } = this.state;
const { schemaUtils, schema } = this.state;

let retrievedSchema = this.state.retrievedSchema;
if (isObject(formData) || Array.isArray(formData)) {
const newState = this.getStateFromProps(this.props, formData, retrievedSchema);
const newState = this.getStateFromProps(this.props, formData);
formData = newState.formData;
retrievedSchema = newState.retrievedSchema;
}

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

/**
* If the retrievedSchema has changed the new retrievedSchema is returned.
* Otherwise, the old retrievedSchema is returned to persist reference.
* - This ensures that AJV retrieves the schema from the cache when it has not changed,
* avoiding the performance cost of recompiling the schema.
*
* @param retrievedSchema The new retrieved schema.
* @returns The new retrieved schema if it has changed, else the old retrieved schema.
*/
private updateRetrievedSchema(retrievedSchema: S) {
const isTheSame = deepEquals(retrievedSchema, this.state?.retrievedSchema);
return isTheSame ? this.state.retrievedSchema : retrievedSchema;
}

/**
* Callback function to handle reset form data.
* - Reset all fields with default values.
Expand Down
50 changes: 50 additions & 0 deletions packages/core/test/ObjectField.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,56 @@ describe('ObjectField', () => {
});
});

it('Check schema with if/then/else conditions and activate the then/else subschemas, the onChange event should reflect the actual validation errors', () => {
const schema = {
type: 'object',
_const: 'test',
required: ['checkbox'],
properties: {
checkbox: {
type: 'boolean',
},
},
if: {
required: ['checkbox'],
properties: {
checkbox: {
const: true,
},
},
},
then: {
required: ['text'],
properties: {
text: {
type: 'string',
},
},
},
};

const { node, onChange } = createFormComponent({
schema,
formData: {
checkbox: true,
},
liveValidate: true,
});

// Uncheck the checkbox
fireEvent.click(node.querySelector('input[type=checkbox]'));

sinon.assert.calledWithMatch(
onChange.lastCall,
{
formData: { checkbox: false },
errorSchema: {},
errors: [],
},
'root_checkbox'
);
});

it('Check that when formData changes, the form should re-validate', () => {
const { node, rerender } = createFormComponent({
schema,
Expand Down
Loading