Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ should change the heading of the (upcoming) version to include a major version b

-->

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

# 5.23.2

## @rjsf/core
Expand Down
24 changes: 21 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,19 @@ export default class Form<
this.setState(state as FormState<T, S, F>, () => onChange && onChange({ ...this.state, ...state }, id));
};

/**
* Returns if the retrievedSchema has changed the new retrievedSchema,
* Else the old retrievedSchema 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