-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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 usages of async-unsafe lifecycle methods #919
Conversation
test/components/connect.spec.js
Outdated
@@ -2121,7 +2120,7 @@ describe('React', () => { | |||
|
|||
@connect(null) | |||
class Parent extends React.Component { | |||
componentWillMount() { | |||
UNSAFE_componentWillMount() { |
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.
When I tried to change it to componentDidMount
it broke the test. Maybe it should be changed to constructor
, I'm not sure
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.
What error did you get with cDM? The cWM function is not consequential here anyways; this is testing for unmount cleanups. So, I'd be fine with removing it and putting a dispatch outside of the ReactDOM.render.
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.
data
was null
src/components/connectAdvanced.js
Outdated
} | ||
|
||
shouldComponentUpdate() { | ||
shouldComponentUpdate(nextProps) { |
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 feels like a hack with bad side effects. Can we use getSnapshotBeforeUpdate
instead?
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.
Will try that. Is it called before sCU?
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 like it's not: https://reactjs.org/docs/react-component.html#updating
I can try to move selector
from field on instance to field on state, then it will be available in static getDerivedStateFromProps
src/components/connectAdvanced.js
Outdated
@@ -248,6 +247,10 @@ export default function connectAdvanced( | |||
|
|||
render() { | |||
const selector = this.selector | |||
|
|||
// Handle forceUpdate | |||
if (!selector.shouldComponentUpdate) selector.run(this.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.
Shouldn't this be handled by sCU above?
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.
sCU doesn't run when forceUpdating
Btw let us know when you'll need a final review for whether it's async-safe. |
@gaearon I think it's ready for review |
test/components/connect.spec.js
Outdated
@@ -2009,7 +2008,7 @@ describe('React', () => { | |||
|
|||
@connect(null, mapDispatchFactory, mergeParentDispatch) | |||
class Passthrough extends Component { | |||
componentWillUpdate() { | |||
componentDIdUpdate() { |
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.
There is a typo here? componentDIdUpdate
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.
Will fix & add test
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 commited a fix for you.
package.json
Outdated
@@ -53,7 +53,8 @@ | |||
"lodash": "^4.17.5", | |||
"lodash-es": "^4.17.5", | |||
"loose-envify": "^1.1.0", | |||
"prop-types": "^15.6.1" | |||
"prop-types": "^15.6.1", | |||
"react-lifecycles-compat": "^1.1.4" |
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 do you need the polyfill when you changed all the deprecated functions?
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.
He's using getDerivedStateFromProps
.
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 am using that too without a polyfill. No more warnings (in my own code).
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 for use with older versions of React. It's not to deal with deprecation warnings, it's to polyfill those lifecycle methods for React <= 16.2.
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 right, didn't think it that way around.
That brings me to a question for my own lib. What if I want to support the new and old Context API I guess I could just check if React.createContext
is a function and then use it if avail?
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.
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.
New context API is probably going to require breaking changes to React Redux's public API for supporting multiple stores, making it a major version bump.
src/components/connectAdvanced.js
Outdated
@@ -129,7 +138,9 @@ export default function connectAdvanced( | |||
`or explicitly pass "${storeKey}" as a prop to "${displayName}".` | |||
) | |||
|
|||
this.initSelector() | |||
this.state = { | |||
selector: this.createSelector() |
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 haven't looked at what StrictMode warns about yet. Does it complain about instance variables? Is that why we're putting the selector into 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.
No it doesn't.
This is because getDerivedStateFromProps
is static, so one can't access instances from it
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.
Ahhh, I see it now. Got it.
src/components/connectAdvanced.js
Outdated
@@ -86,6 +91,11 @@ export default function connectAdvanced( | |||
[subscriptionKey]: subscriptionShape, | |||
} | |||
|
|||
function getDerivedStateFromProps(nextProps, prevState) { | |||
prevState.selector.run(nextProps) |
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 looks mutative. What does it actually do?
I'm worried that this won't be safe and then avoiding the warning just gives a false sense of security.
The method signature is intentionally "get" to indicate it should be pure.
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 not pure but it's idempotent: when you run in twice, it has the same effect as if it was just once.
I can try to make it pure by moving all the fields from selector
to state though.
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.
Let's try that!
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.
Did that. It required some minor changes in tests: a7ba298#diff-b5fd7a42ea1b8efa382416b4a323003c
It's quite clear why setState
calls count changed, but as for mapStateToPropsCalls
, I'm not so confident
Well, now that the changes are becoming more significant, I wonder if we should pick #856 back up. |
I'm testing with this fork and components subscribed to redux state using |
@peacechen it works OK on my project. Can you share some parts of your setup (maybe as a reproduction repo), so that I could debug it? |
It's a pretty large app. I'll try to create a small sample project to recreate the behavior. |
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'll pull this into some apps to see what breaks. But I'm not seeing any major concerns with the changes proposed.
I would be interested in combining with #856 to move to a full React 16 dep. We still need to do 16.0-16.2 support, so the polyfill is necessary. But I don't consider that a huge issue for the moment.
Yeah, I suspect there's a lot of overlap between the two. I assume this would probably merit a v6.0 major version? So, notionally v6 would be "compat with React 16.3+", and v7 would be "super-duper React async rendering awesomeness" . |
6.0 would be 16+. This supports <16.2, and Jim's PR does 16+. So, 16.0-16.2 would be covered. Definitely a major, though. |
Anyone else want to second this? I can push a 5.1.0-beta.1 to give it a whirl elsewhere. |
I could try to integrate it into a small project of mine and leave some feedback, once the beta is out on npm. |
OK, I'm going to merge this in and push a 5.1.0-test.1 release. Please give this a whirl! We can look into some of the BC-breaking stuff next. |
Found a big bug with the gDSFP change in 16.4: https://reactjs.org/blog/2018/05/23/react-v-16-4.html#bugfix-for-getderivedstatefromprops Because it's getting called for any render, it's getting double-called for most updates. That leads to it setting the sCU flag to true and then false on the 2nd call. I've got to do a deeper dive, but I don't have time at the moment. |
Well, I think (hope) it's just an issue with our test suite. So, |
* Reformat to make VS Code less annoying * Failing test in <StrictMode> * Remove usages of async-unsafe lifecycle methods * Remove usage of UNSAFE_componentWillMount in test * Move `selector` to state in order to be able to use gDSFP * codeDIdCommit * Stop using mutation * Upgrade react-lifecycles-compat
* Reformat to make VS Code less annoying * Failing test in <StrictMode> * Remove usages of async-unsafe lifecycle methods * Remove usage of UNSAFE_componentWillMount in test * Move `selector` to state in order to be able to use gDSFP * codeDIdCommit * Stop using mutation * Upgrade react-lifecycles-compat
Fixes #897