diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc9185c37..ee675270eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ should change the heading of the (upcoming) version to include a major version b # 5.18.0 +## @rjsf/core + +- Fix Error state not resetting when schema changes [#4079](https://github.com/rjsf-team/react-jsonschema-form/issues/4079) + ## @rjsf/utils - Added a new `skipEmptyDefault` option in `emptyObjectFields`, fixing [#3880](https://github.com/rjsf-team/react-jsonschema-form/issues/3880) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index e4d771c8ec..31752c41e0 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -306,10 +306,16 @@ export default class Form< prevState: FormState ): { nextState: FormState; shouldUpdate: true } | { shouldUpdate: false } { if (!deepEquals(this.props, prevProps)) { + const isSchemaChanged = !deepEquals(prevProps.schema, this.props.schema); + const isFormDataChanged = !deepEquals(prevProps.formData, this.props.formData); const nextState = this.getStateFromProps( this.props, this.props.formData, - prevProps.schema !== this.props.schema ? undefined : this.state.retrievedSchema + // If the `schema` has changed, we need to update the retrieved schema. + // Or if the `formData` changes, for example in the case of a schema with dependencies that need to + // match one of the subSchemas, the retrieved schema must be updated. + isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema, + isSchemaChanged ); const shouldUpdate = !deepEquals(nextState, prevState); return { nextState, shouldUpdate }; @@ -357,9 +363,16 @@ export default class Form< * * @param props - The props passed to the `Form` * @param inputFormData - The new or current data for the `Form` + * @param retrievedSchema - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`. + * @param isSchemaChanged - A flag indicating whether the schema has changed. * @returns - The new state for the `Form` */ - getStateFromProps(props: FormProps, inputFormData?: T, retrievedSchema?: S): FormState { + getStateFromProps( + props: FormProps, + inputFormData?: T, + retrievedSchema?: S, + isSchemaChanged = false + ): FormState { const state: FormState = this.state || {}; const schema = 'schema' in props ? props.schema : this.props.schema; const uiSchema: UiSchema = ('uiSchema' in props ? props.uiSchema! : this.props.uiSchema!) || {}; @@ -382,7 +395,8 @@ export default class Form< const _retrievedSchema = retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData); const getCurrentErrors = (): ValidationData => { - if (props.noValidate) { + // If the `props.noValidate` option is set or the schema has changed, we reset the error state. + if (props.noValidate || isSchemaChanged) { return { errors: [], errorSchema: {} }; } else if (!props.liveValidate) { return { diff --git a/packages/core/test/Form.test.jsx b/packages/core/test/Form.test.jsx index 43c4f43804..efd264a191 100644 --- a/packages/core/test/Form.test.jsx +++ b/packages/core/test/Form.test.jsx @@ -4013,6 +4013,47 @@ describe('Form omitExtraData and liveOmit', () => { // We use setTimeout with a delay of 0ms to allow all asynchronous operations to complete in the React component. // Despite this being a workaround, it turned out to be the only effective method to handle this test case. }); + + it('should reset when schema changes', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + }, + required: ['foo'], + }; + + const { comp, node } = createFormComponent({ + schema, + }); + + Simulate.submit(node); + expect(comp.state.errorSchema).eql({ foo: { __errors: ["must have required property 'foo'"] } }); + expect(comp.state.errors).eql([ + { + message: "must have required property 'foo'", + property: 'foo', + name: 'required', + params: { + missingProperty: 'foo', + }, + schemaPath: '#/required', + stack: "must have required property 'foo'", + }, + ]); + + // Changing schema to reset errors state. + setProps(comp, { + schema: { + type: 'object', + properties: { + foo: { type: 'string' }, + }, + }, + }); + expect(comp.state.errorSchema).eql({}); + expect(comp.state.errors).eql([]); + }); }); it('should keep schema errors when extraErrors set after submit and liveValidate is false', () => {