Skip to content
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

Remove componentWillReceiveProps from Form.jsx #2010

Closed
131 changes: 72 additions & 59 deletions packages/core/src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@ import {
retrieveSchema,
shouldRender,
toIdSchema,
getDefaultRegistry,
deepEquals,
toPathSchema,
isObject,
getDefaultRegistry,
} from "../utils";
import validateFormData, { toErrorList } from "../validate";
import { mergeObjects } from "../utils";

function handleChange(props, state) {
const { lastProps, ...formState } = state;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figure out -- why do we need lastProps?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On line 62 we need to compare prop changes to determine if the consumer has updated their props.

If the consumer has updated the props, we update the internal state based on the props.

We could previously do this in componentWillReceiveProps since that method supports both viewing previous and new props. But since getDerivedStateFromProps only shows the new prop updates, we need to store the old props in the state.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the reason why we cannot always derive state from props, is due to us supporting the mixed controlled/uncontrolled behavior we discussed on our last meeting this Friday.

props.onChange(formState);
}

export default class Form extends Component {
static defaultProps = {
uiSchema: {},
Expand All @@ -31,41 +36,48 @@ export default class Form extends Component {

constructor(props) {
super(props);
this.state = this.getStateFromProps(props, props.formData);
this.state = Form.getStateFromProps(props, props.formData);
}

formElement = null;

componentDidMount() {
if (
this.props.onChange &&
!deepEquals(this.state.formData, this.props.formData)
) {
this.props.onChange(this.state);
handleChange(this.props, this.state);
}
this.formElement = null;
}

UNSAFE_componentWillReceiveProps(nextProps) {
const nextState = this.getStateFromProps(nextProps, nextProps.formData);
componentDidUpdate(prevProps) {
if (
!deepEquals(nextState.formData, nextProps.formData) &&
!deepEquals(nextState.formData, this.state.formData) &&
this.props.onChange
this.props.onChange &&
!deepEquals(this.state.formData, this.props.formData)
) {
this.props.onChange(nextState);
handleChange(this.props, this.state);
}
this.setState(nextState);
}

getStateFromProps(props, inputFormData) {
const state = this.state || {};
const schema = "schema" in props ? props.schema : this.props.schema;
const uiSchema = "uiSchema" in props ? props.uiSchema : this.props.uiSchema;
static getDerivedStateFromProps(props, state) {
if (!deepEquals(props, state.lastProps)) {
return Form.getStateFromProps(props, props.formData, state);
}
return null;
}

static getStateFromProps(props, inputFormData, state = {}) {
const edit = typeof inputFormData !== "undefined";
const liveValidate =
"liveValidate" in props ? props.liveValidate : this.props.liveValidate;
const mustValidate = edit && !props.noValidate && liveValidate;
const rootSchema = schema;
const formData = getDefaultFormState(schema, inputFormData, rootSchema);
const retrievedSchema = retrieveSchema(schema, rootSchema, formData);
const customFormats = props.customFormats;
const additionalMetaSchemas = props.additionalMetaSchemas;
const mustValidate = edit && !props.noValidate && props.liveValidate;
const formData = getDefaultFormState(
props.schema,
inputFormData,
props.schema
);
const retrievedSchema = retrieveSchema(
props.schema,
props.schema,
formData
);

const getCurrentErrors = () => {
if (props.noValidate) {
Expand All @@ -87,11 +99,12 @@ export default class Form extends Component {
schemaValidationErrors,
schemaValidationErrorSchema;
if (mustValidate) {
const schemaValidation = this.validate(
const schemaValidation = Form.validate(
formData,
schema,
additionalMetaSchemas,
customFormats
props,
props.schema,
props.additionalMetaSchemas,
props.customFormats
);
errors = schemaValidation.errors;
errorSchema = schemaValidation.errorSchema;
Expand All @@ -114,20 +127,21 @@ export default class Form extends Component {
}
const idSchema = toIdSchema(
retrievedSchema,
uiSchema["ui:rootFieldId"],
rootSchema,
props.uiSchema["ui:rootFieldId"],
props.schema,
formData,
props.idPrefix
);
const nextState = {
schema,
uiSchema,
schema: props.schema,
uiSchema: props.uiSchema,
idSchema,
formData,
edit,
errors,
errorSchema,
additionalMetaSchemas,
additionalMetaSchemas: props.additionalMetaSchemas,
lastProps: props,
};
if (schemaValidationErrors) {
nextState.schemaValidationErrors = schemaValidationErrors;
Expand All @@ -136,18 +150,15 @@ export default class Form extends Component {
return nextState;
}

shouldComponentUpdate(nextProps, nextState) {
return shouldRender(this, nextProps, nextState);
}

validate(
static validate(
formData,
schema = this.props.schema,
additionalMetaSchemas = this.props.additionalMetaSchemas,
customFormats = this.props.customFormats
props,
schema = props.schema,
additionalMetaSchemas = props.additionalMetaSchemas,
customFormats = props.customFormats
) {
const { validate, transformErrors } = this.props;
const { rootSchema } = this.getRegistry();
const { validate, transformErrors } = props;
const { rootSchema } = Form.getRegistry(props);
const resolvedSchema = retrieveSchema(schema, rootSchema, formData);
return validateFormData(
formData,
Expand All @@ -159,6 +170,10 @@ export default class Form extends Component {
);
}

shouldComponentUpdate(nextProps, nextState) {
return shouldRender(this, nextProps, nextState);
}

renderErrors() {
const { errors, errorSchema, schema, uiSchema } = this.state;
const { ErrorList, showErrorList, formContext } = this.props;
Expand Down Expand Up @@ -222,7 +237,7 @@ export default class Form extends Component {

onChange = (formData, newErrorSchema) => {
if (isObject(formData) || Array.isArray(formData)) {
const newState = this.getStateFromProps(this.props, formData);
const newState = Form.getStateFromProps(this.props, formData, this.state);
formData = newState.formData;
}
const mustValidate = !this.props.noValidate && this.props.liveValidate;
Expand Down Expand Up @@ -251,7 +266,7 @@ export default class Form extends Component {
}

if (mustValidate) {
let schemaValidation = this.validate(newFormData);
let schemaValidation = Form.validate(newFormData, this.props);
let errors = schemaValidation.errors;
let errorSchema = schemaValidation.errorSchema;
const schemaValidationErrors = errors;
Expand Down Expand Up @@ -287,7 +302,7 @@ export default class Form extends Component {
}
this.setState(
state,
() => this.props.onChange && this.props.onChange(this.state)
() => this.props.onChange && handleChange(this.props, this.state)
);
};

Expand Down Expand Up @@ -331,7 +346,7 @@ export default class Form extends Component {
}

if (!this.props.noValidate) {
let schemaValidation = this.validate(newFormData);
let schemaValidation = Form.validate(newFormData, this.props);
let errors = schemaValidation.errors;
let errorSchema = schemaValidation.errorSchema;
const schemaValidationErrors = errors;
Expand Down Expand Up @@ -387,19 +402,17 @@ export default class Form extends Component {
);
};

getRegistry() {
// For BC, accept passed SchemaField and TitleField props and pass them to
// the "fields" registry one.
static getRegistry(props) {
const { fields, widgets } = getDefaultRegistry();
return {
fields: { ...fields, ...this.props.fields },
widgets: { ...widgets, ...this.props.widgets },
ArrayFieldTemplate: this.props.ArrayFieldTemplate,
ObjectFieldTemplate: this.props.ObjectFieldTemplate,
FieldTemplate: this.props.FieldTemplate,
definitions: this.props.schema.definitions || {},
rootSchema: this.props.schema,
formContext: this.props.formContext || {},
fields: { ...fields, ...props.fields },
widgets: { ...widgets, ...props.widgets },
ArrayFieldTemplate: props.ArrayFieldTemplate,
ObjectFieldTemplate: props.ObjectFieldTemplate,
FieldTemplate: props.FieldTemplate,
definitions: props.schema.definitions || {},
rootSchema: props.schema,
formContext: props.formContext || {},
};
}

Expand Down Expand Up @@ -434,7 +447,7 @@ export default class Form extends Component {
} = this.props;

const { schema, uiSchema, formData, errorSchema, idSchema } = this.state;
const registry = this.getRegistry();
const registry = Form.getRegistry(this.props);
const _SchemaField = registry.fields.SchemaField;
const FormTag = tagName ? tagName : "form";
if (deprecatedAutocomplete) {
Expand Down