-
Notifications
You must be signed in to change notification settings - Fork 47.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
Add warning to prevent setting this.state to this.props referentially #11658
Conversation
Most people don't know what "referentially" means. I would reword this as:
|
Updated to include the new warning message and component name. Ran |
@@ -465,6 +465,19 @@ export default function( | |||
instance.refs = emptyObject; | |||
instance.context = getMaskedContext(workInProgress, unmaskedContext); | |||
|
|||
const noSetStatesFromProps = !(instance.state === instance.props); | |||
warning( | |||
noSetStatesFromProps, |
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.
I know this is consistent with the rest of the warnings, but I wonder if we should just inline noSetStatesFromProps
and avoid the whole negation thing?
warning(
instance.state !== instance.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.
That looks better to me, as well. Should I make the edit? Edit: Made the edit and pushed a new commit.
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 guidance is not clear here tbh. i've seen @gaearon also prefer warning(false,....
and doing all the logic in an if block. ¯_(ツ)_/¯
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.
Typically we do warning(false, "")
when we also need to execute some other code when issuing the warning, such as setting a flag so that the warning is deduped.
expectDev(console.error.calls.count()).toBe(1); | ||
expectDev(console.error.calls.argsFor(0)[0]).toContain( | ||
'It looks like the StatefulComponent component contains a line like this in its constructor: ' + | ||
'this.state = 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.
Have you checked how this looks in a browser? Since you didn't add the linebreaks I assume it will look a bit confusing.
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.
Good point. Pushed a new commit with line breaks before and after the code excerpt to increase readability.
Looks like CircleCI is failing due to the same CI/Docker issue (nodejs/docker-node#651) referenced by @bvaughn in #12392.
Lint, flow, and tests are passing locally. Anyway, ready for your review, @gaearon! |
'something from the props, do it during the rendering. If you need to ' + | ||
'share the state between several components, move it to their closest ' + | ||
'common ancestor and pass it down as props to them.', | ||
getComponentName(workInProgress) || 'Unknown', |
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.
Just a small nit: it would probably be a better idea to pass A component
instead of Unknown
if no component name is found.
Your example:
It looks like the Unknown component contains a line ...
This formatting seems quite odd to me. What the Unknown component? What if I in a rare case I have my own Unknown component and it's not the erroring one?
Suggestion:
A component contains a line ...
or if the component name exists
Foo contains a line like this...
This would also keep the handling of missing component names in error messages consistent. See: ReactFiberTreeReflection.js, ReactDOM.js
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.
I'm inclined to agree, but as I searched through the code base I saw the getComponentName(*) || 'Unknown'
pattern used 22 times while Component
or A component
were only used 7 and 8 times respectively. In fact, the most recent commits by @gaearon, @acdlite, and @bvaughn all use Unknown
(#12028, #12359, #11455). I'll let one of them chime in about whether they think this should be changed.
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.
It's true that "Unknown" is probably a more common fallback name, but it's not strictly necessary to use that pattern. That being said, I think this warning is a bit verbose and maybe tries to cover too broad a topic (e.g. sharing state between several components).
At first glance, I would prefer a shorter message, something like:
warning(
instance.state !== instance.props,
"%: It is not recommended to assign props directly to state. " +
"Updates to props won't be reflected in state. " +
"It's usually better to just use props directly.",
getComponentName(workInProgress) || "Unknown"
);
That being said, I realize this contradicts an earlier suggest from Dan so I'll defer if there are stronger preferences there.
It's been a couple weeks without comment, so I incorporated @bvaughn's and @raunofreiberg's input into the latest iteration. Thanks, you two, I think your edits make it more semantic and terse. Pinging @gaearon |
"because updates to props won't be reflected in state." + | ||
'In most cases, it is better to use props directly.', | ||
getComponentName(workInProgress) || 'A component', | ||
); |
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.
This needs to be within an if (__DEV__)
check. Suggest using the one right below (line 711).
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.
Fixed!
warning( | ||
instance.state !== instance.props, | ||
'%s: It is not recommended to assign props directly to state' + | ||
"because updates to props won't be reflected in 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.
Need a trailing space (" ") 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.
Fixed!
'%s: It is not recommended to assign props directly to state ' + | ||
"because updates to props won't be reflected in state. " + | ||
'In most cases, it is better to use props directly.', | ||
getComponentName(workInProgress) || 'A component', |
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.
nit: I think we generally use "Component" as the default name in warnings when it's followed by ":" (e.g. "Component: blah". We use "A component" when it's part of a sentence (e.g. "A component blah")
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.
Left a nit but we could do this post-merge.
LGTM otherwise.
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.
This warning should be deduplicated by component name. Otherwise it'll spam the console for any component that's used in a large number of places.
Nice catch 😄 |
Latest commit should address @gaearon's request! |
if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) { | ||
didWarnAboutDirectlyAssigningPropsToState.add(componentName); | ||
warning( | ||
instance.state !== instance.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.
I think we should move this check up a level:
if (__DEV__) {
if (instance.state === instance.props) {
const componentName = getComponentName(workInProgress) || 'Component';
if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) {
didWarnAboutDirectlyAssigningPropsToState.add(componentName);
warning(false, ...);
This way we don't add things to the Set unnecessarily, and we also don't potentially suppress a later warning (e.g. if we encounter 2 components with the same name- and the second one assigns props to state but the first one doesn't).
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, yes, of course. Thanks for catching that. (Edit: fixed)
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.
👍 Looks good to me.
Details of bundled changes.Comparing: 29287f0...4268592 react-dom
react-art
react-test-renderer
react-reconciler
react-native-renderer
Generated by 🚫 dangerJS |
Hey @gaearon, just want to ping you again about this PR |
Thanks! |
@stnwk We output all warnings through |
Okay, thanks for the explanation :) |
…facebook#11658) * add test to warn if setting this.state to this.props referentially * Avoid an extra check
Adds warning to prevent setting
this.state = props
. Closes #11593.yarn test
yarn prettier
yarn lint
yarn flow
Thanks for your help on this first PR, @sw-yx and @gaearon.