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

Conversation

jimmycallin
Copy link
Collaborator

@jimmycallin jimmycallin commented Aug 22, 2020

Reasons for making this change

componentWillReceiveProps is deprecated, and is currently in use in two places in this project, which causes annoying warnings.

This PR removes the one in Form.jsx, including some refactoring (required due to getDerivedStateFromProps is a static method).

Checklist

  • I'm updating documentation
  • I'm adding or updating code
    • I've added and/or updated tests
    • I've updated docs if needed
  • I'm adding a new feature
    • I've updated the playground with an example use of the feature

@jimmycallin
Copy link
Collaborator Author

What makes this refactoring particularly complex is how rjsf handles updates of formData from props and internally. It allows for mixing updates from formData and/or internal state changes, and checks previous formData states to determine whether or not to trigger onChange events. All this would be much easier if the rules was more like:

  1. If props has formData set, do not save formData state internally and let the parent component control formData entirely by listening to onChange events.
  2. If it does not have formData set, save formData state internally and trigger onChange events on changes.

This would greatly simplify the logic without causing any regressions (I think), but I believe this would be a breaking change and would be an overall bigger task.

schemaValidationErrors = state.schemaValidationErrors;
schemaValidationErrorSchema = state.schemaValidationErrorSchema;
static getDerivedStateFromProps(props, state) {
if (!deepEquals(state.lastProps, props)) {
Copy link

@RoboPhred RoboPhred Aug 31, 2020

Choose a reason for hiding this comment

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

Why the reliance on deep equals for comparing property references? Does RJSF really want to support mutating object references? Such an action will not cause react to re-render the component anyway, so supporting this seems to be just a big performance sink.

We are working with very large forms (several hundered fields), and this area has been a pain point for us. I have an outstanding question about the issue here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hi @RoboPhred, from my view there is really no reason to use the deepEquals. I didn't spend any thought in reevaluating this, since I just wanted to have a PR with no breaking changes. I tried to replace deepEquals with shallow equals, and it didn't seem to break any tests, so I just pushed this change. We'll see if it passes review. :)

Choose a reason for hiding this comment

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

Whoops, that change might not work, as you will always receive a new core props reference each time react re-renders, meaning it will try to make a new state every render. You should compare each individual property to its previous value.

You might be able to get away with something like:

const keys = Object.keys(props);
if (keys.some(key => state.lastProps[key] != props[key]) {
  // regenerate state
}

I am a bit worried about adding or removing properties, but I /think/ that if you have an undefined property passed in react, react will actively create that property and set its value as undefined, meaning the property will still show up in Object.keys(). I don't think there are any cases where we might have a property defined in lastProps but not defined in props.

For reference, you can see my previous attempt at this here. In that case, I individually checked the props by name, but I think we can get away with a bulk scan over the new properties.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, good catch. I added an additional check that checked the length of the number of properties, which I think will fix the case of any removed properties not triggering an onChange event. Not sure why the Netlify CI tests are breaking, but I think that's unrelated.

@RoboPhred
Copy link

  1. If props has formData set, do not save formData state internally and let the parent component control formData entirely by listening to onChange events.

This would go a significant way to fixing this issue, although the continued use of deep equals still needs to be addressed.

@epicfaace
Copy link
Member

@jimmycallin hey, I think this is a great change! Just haven't had a chance to review it yet. I'll put it on my list and try to get this in soon.

@epicfaace epicfaace self-assigned this Apr 14, 2021
@epicfaace epicfaace added the p1 Important to fix soon label Apr 14, 2021
@Zakhar-Kutsko
Copy link

Hi there! When you will merge these changes and release a new version?

@epicfaace
Copy link
Member

It's on the agenda for a sync review in a future meeting https://docs.google.com/document/d/12PjTvv21k6LIky6bNQVnsplMLLnmEuypTLQF8a-8Wss/edit

Copy link
Member

@epicfaace epicfaace left a comment

Choose a reason for hiding this comment

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

@jimmycallin can you join us at one of our synchronous meetings so you can explain the PR to us? You can add the PR to the agenda of a day in which you're able to make it. Thanks! https://docs.google.com/document/d/12PjTvv21k6LIky6bNQVnsplMLLnmEuypTLQF8a-8Wss/edit

@@ -1265,3 +1265,115 @@ export function schemaRequiresTrueValue(schema) {

return false;
}

export function getRegistry(props) {
Copy link
Member

Choose a reason for hiding this comment

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

We should move all of these new functions in utils.js to just Form.js (because they're quite Form-specific)

Copy link
Member

Choose a reason for hiding this comment

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

can we just make them static functions (such as static Registry) within Form? This will make it easier to compare changes in the PR.

packages/core/src/components/Form.js Outdated Show resolved Hide resolved
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.

}

export function getStateFromProps(props, inputFormData, state = {}) {
const edit = typeof inputFormData !== "undefined";
Copy link
Member

Choose a reason for hiding this comment

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

We should figure out -- why does this omit several lines from the original getStateFromProps function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the lines omitted are mostly just variable declarations that I pick up directly from the prop object instead

@@ -1265,3 +1265,115 @@ export function schemaRequiresTrueValue(schema) {

return false;
}

export function getRegistry(props) {
Copy link
Member

Choose a reason for hiding this comment

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

can we just make them static functions (such as static Registry) within Form? This will make it easier to compare changes in the PR.

@jimmycallin
Copy link
Collaborator Author

@epicfaace i have now updated the PR with all your proposed changes and questions, and merged the lastest from master. so it's ready for another round of review :)

Copy link
Member

@epicfaace epicfaace left a comment

Choose a reason for hiding this comment

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

Per our meeting -- deepEquals(props, state.lastProps) is probably too expensive / too much of a performance hit, so let's instead actually change some of the logic so that the component behaves more like a controlled / uncontrolled component.

Maybe we could let the user either

  • pass in onChange and formData, which makes the Form component fully controlled, or
  • not do so, which would make the Form component fully uncontrolled.

@jimmycallin , can you write up (or tell us in a future call) how the current behavior of the form works with formdata / onchange? We don't fully understand it.

@ArtsiomAntropau
Copy link

Hi, @jimmycallin, do you have the time and desire to finish this PR?
Our team can contribute to this PR if it is needed, and you with @epicfaace will highlight what needs to be improved.

@jimmycallin
Copy link
Collaborator Author

@ArtsiomAntropau Hi! I have fairly limited time nowadays, and I'm not using rjsf in my work anymore, so if your team can take over this task I would greatly appreciate it.

As @epicfaace mentioned above, this PR in its current state is not a viable solution. We discussed options in a weekly meeting a few weeks ago, and decided that a breaking change where we make the form component a proper controlled/uncontrolled component as described in these two comments makes more sense:

#2010 (comment)
#2010 (review)

If you are willing to implement this, it would be very much appreciated.

@cdolek
Copy link

cdolek commented Mar 31, 2022

Hi guys, is there a middle step where UNSAFE_componentWillReceiveProps is not used anymore, and then the breaking change is introduced with refactoring?

@jimmycallin
Copy link
Collaborator Author

Since this isn't a viable approach for removing componentWillReceiveProps, I'll close this one. Due to personal reasons I don't think I will be able to redo the PR according to the previous comment, so if someone else wants to step in, please do so.

@jimmycallin jimmycallin closed this Apr 3, 2022
@Kos
Copy link

Kos commented Apr 4, 2022

Is there a relevant issue which we could follow for updates regarding this bit?

We discussed options in a #2677 a few weeks ago, and decided that a breaking change where we make the form component a proper controlled/uncontrolled component as described in these two comments makes more sense

@Zakhar-Kutsko
Copy link

Zakhar-Kutsko commented Apr 5, 2022

Hi there! Following the documentation, by default, <Form /> is already an uncontrolled component.@epicfaace, Or is your suggestion exactly to remove deepEquals(props, state.lastProps)?

@cdolek
Copy link

cdolek commented Apr 7, 2022

@ArtsiomAntropau is your team working on this?

@ArtsiomAntropau
Copy link

@cdolek Hi,
@Zakhar-Kutsko still investigating on it

@summer-cook
Copy link

Hi @ArtsiomAntropau , was just wondering if there is an open issue for this I could follow? Still getting this warning on our Form

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p1 Important to fix soon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants