-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Remove componentWillReceiveProps from Form.jsx #2010
Conversation
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:
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. |
f2d6f37
to
a9d8e05
Compare
packages/core/src/components/Form.js
Outdated
schemaValidationErrors = state.schemaValidationErrors; | ||
schemaValidationErrorSchema = state.schemaValidationErrorSchema; | ||
static getDerivedStateFromProps(props, state) { | ||
if (!deepEquals(state.lastProps, props)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. :)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
This would go a significant way to fixing this issue, although the continued use of deep equals still needs to be addressed. |
@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. |
Hi there! When you will merge these changes and release a new version? |
It's on the agenda for a sync review in a future meeting https://docs.google.com/document/d/12PjTvv21k6LIky6bNQVnsplMLLnmEuypTLQF8a-8Wss/edit |
There was a problem hiding this 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
packages/core/src/utils.js
Outdated
@@ -1265,3 +1265,115 @@ export function schemaRequiresTrueValue(schema) { | |||
|
|||
return false; | |||
} | |||
|
|||
export function getRegistry(props) { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
import { mergeObjects } from "../utils"; | ||
|
||
function handleChange(props, state) { | ||
const { lastProps, ...formState } = state; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
packages/core/src/utils.js
Outdated
} | ||
|
||
export function getStateFromProps(props, inputFormData, state = {}) { | ||
const edit = typeof inputFormData !== "undefined"; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
packages/core/src/utils.js
Outdated
@@ -1265,3 +1265,115 @@ export function schemaRequiresTrueValue(schema) { | |||
|
|||
return false; | |||
} | |||
|
|||
export function getRegistry(props) { |
There was a problem hiding this comment.
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.
@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 :) |
There was a problem hiding this 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.
Hi, @jimmycallin, do you have the time and desire to finish this PR? |
@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) If you are willing to implement this, it would be very much appreciated. |
Hi guys, is there a middle step where |
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. |
Is there a relevant issue which we could follow for updates regarding this bit?
|
Hi there! Following the documentation, by default, |
@ArtsiomAntropau is your team working on this? |
@cdolek Hi, |
Hi @ArtsiomAntropau , was just wondering if there is an open issue for this I could follow? Still getting this warning on our Form |
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