-
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
React lifecycles compat #12105
React lifecycles compat #12105
Changes from all commits
dc5b266
7c4bbf6
a3635c9
bd88689
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -499,7 +499,7 @@ describe('ReactComponentLifeCycle', () => { | |
expect(instance.state.stateField).toBe('goodbye'); | ||
}); | ||
|
||
it('should call nested lifecycle methods in the right order', () => { | ||
it('should call nested legacy lifecycle methods in the right order', () => { | ||
let log; | ||
const logger = function(msg) { | ||
return function() { | ||
|
@@ -509,11 +509,6 @@ describe('ReactComponentLifeCycle', () => { | |
}; | ||
}; | ||
class Outer extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps(props, prevState) { | ||
log.push('outer getDerivedStateFromProps'); | ||
return null; | ||
} | ||
UNSAFE_componentWillMount = logger('outer componentWillMount'); | ||
componentDidMount = logger('outer componentDidMount'); | ||
UNSAFE_componentWillReceiveProps = logger( | ||
|
@@ -533,11 +528,6 @@ describe('ReactComponentLifeCycle', () => { | |
} | ||
|
||
class Inner extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps(props, prevState) { | ||
log.push('inner getDerivedStateFromProps'); | ||
return null; | ||
} | ||
UNSAFE_componentWillMount = logger('inner componentWillMount'); | ||
componentDidMount = logger('inner componentDidMount'); | ||
UNSAFE_componentWillReceiveProps = logger( | ||
|
@@ -554,18 +544,9 @@ describe('ReactComponentLifeCycle', () => { | |
|
||
const container = document.createElement('div'); | ||
log = []; | ||
expect(() => ReactDOM.render(<Outer x={1} />, container)).toWarnDev([ | ||
'Warning: Outer: Defines both componentWillReceiveProps() and static ' + | ||
'getDerivedStateFromProps() methods. ' + | ||
'We recommend using only getDerivedStateFromProps().', | ||
'Warning: Inner: Defines both componentWillReceiveProps() and static ' + | ||
'getDerivedStateFromProps() methods. ' + | ||
'We recommend using only getDerivedStateFromProps().', | ||
]); | ||
ReactDOM.render(<Outer x={1} />, container); | ||
expect(log).toEqual([ | ||
'outer getDerivedStateFromProps', | ||
'outer componentWillMount', | ||
'inner getDerivedStateFromProps', | ||
'inner componentWillMount', | ||
'inner componentDidMount', | ||
'outer componentDidMount', | ||
|
@@ -576,11 +557,9 @@ describe('ReactComponentLifeCycle', () => { | |
ReactDOM.render(<Outer x={2} />, container); | ||
expect(log).toEqual([ | ||
'outer componentWillReceiveProps', | ||
'outer getDerivedStateFromProps', | ||
'outer shouldComponentUpdate', | ||
'outer componentWillUpdate', | ||
'inner componentWillReceiveProps', | ||
'inner getDerivedStateFromProps', | ||
'inner shouldComponentUpdate', | ||
'inner componentWillUpdate', | ||
'inner componentDidUpdate', | ||
|
@@ -595,6 +574,131 @@ describe('ReactComponentLifeCycle', () => { | |
]); | ||
}); | ||
|
||
it('should call nested new lifecycle methods in the right order', () => { | ||
let log; | ||
const logger = function(msg) { | ||
return function() { | ||
// return true for shouldComponentUpdate | ||
log.push(msg); | ||
return true; | ||
}; | ||
}; | ||
class Outer extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps(props, prevState) { | ||
log.push('outer getDerivedStateFromProps'); | ||
return null; | ||
} | ||
componentDidMount = logger('outer componentDidMount'); | ||
shouldComponentUpdate = logger('outer shouldComponentUpdate'); | ||
componentDidUpdate = logger('outer componentDidUpdate'); | ||
componentWillUnmount = logger('outer componentWillUnmount'); | ||
render() { | ||
return ( | ||
<div> | ||
<Inner x={this.props.x} /> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
class Inner extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps(props, prevState) { | ||
log.push('inner getDerivedStateFromProps'); | ||
return null; | ||
} | ||
componentDidMount = logger('inner componentDidMount'); | ||
shouldComponentUpdate = logger('inner shouldComponentUpdate'); | ||
componentDidUpdate = logger('inner componentDidUpdate'); | ||
componentWillUnmount = logger('inner componentWillUnmount'); | ||
render() { | ||
return <span>{this.props.x}</span>; | ||
} | ||
} | ||
|
||
const container = document.createElement('div'); | ||
log = []; | ||
ReactDOM.render(<Outer x={1} />, container); | ||
expect(log).toEqual([ | ||
'outer getDerivedStateFromProps', | ||
'inner getDerivedStateFromProps', | ||
'inner componentDidMount', | ||
'outer componentDidMount', | ||
]); | ||
|
||
// Dedup warnings | ||
log = []; | ||
ReactDOM.render(<Outer x={2} />, container); | ||
expect(log).toEqual([ | ||
'outer getDerivedStateFromProps', | ||
'outer shouldComponentUpdate', | ||
'inner getDerivedStateFromProps', | ||
'inner shouldComponentUpdate', | ||
'inner componentDidUpdate', | ||
'outer componentDidUpdate', | ||
]); | ||
|
||
log = []; | ||
ReactDOM.unmountComponentAtNode(container); | ||
expect(log).toEqual([ | ||
'outer componentWillUnmount', | ||
'inner componentWillUnmount', | ||
]); | ||
}); | ||
|
||
it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => { | ||
class Component extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps() { | ||
return null; | ||
} | ||
componentWillMount() { | ||
throw Error('unexpected'); | ||
} | ||
componentWillReceiveProps() { | ||
throw Error('unexpected'); | ||
} | ||
componentWillUpdate() { | ||
throw Error('unexpected'); | ||
} | ||
render() { | ||
return null; | ||
} | ||
} | ||
|
||
const container = document.createElement('div'); | ||
expect(() => ReactDOM.render(<Component />, container)).toWarnDev( | ||
'Defines both componentWillReceiveProps', | ||
); | ||
}); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth adding a similar test for unsafe lifecycles maybe? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure 👍 |
||
it('should not invoke new unsafe lifecycles (cWM/cWRP/cWU) if static gDSFP is present', () => { | ||
class Component extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps() { | ||
return null; | ||
} | ||
UNSAFE_componentWillMount() { | ||
throw Error('unexpected'); | ||
} | ||
UNSAFE_componentWillReceiveProps() { | ||
throw Error('unexpected'); | ||
} | ||
UNSAFE_componentWillUpdate() { | ||
throw Error('unexpected'); | ||
} | ||
render() { | ||
return null; | ||
} | ||
} | ||
|
||
const container = document.createElement('div'); | ||
expect(() => ReactDOM.render(<Component />, container)).toWarnDev( | ||
'Defines both componentWillReceiveProps', | ||
); | ||
}); | ||
|
||
it('calls effects on module-pattern component', function() { | ||
const log = []; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,29 @@ describe('ReactDOMServerLifecycles', () => { | |
ReactDOMServer = require('react-dom/server'); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.resetModules(); | ||
}); | ||
|
||
it('should not invoke cWM if static gDSFP is present', () => { | ||
class Component extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps() { | ||
return null; | ||
} | ||
componentWillMount() { | ||
throw Error('unexpected'); | ||
} | ||
render() { | ||
return null; | ||
} | ||
} | ||
|
||
expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev( | ||
'Component: componentWillMount() is deprecated and will be removed in the next major version.', | ||
); | ||
}); | ||
|
||
// TODO (RFC #6) Merge this back into ReactDOMServerLifecycles-test once | ||
// the 'warnAboutDeprecatedLifecycles' feature flag has been removed. | ||
it('should warn about deprecated lifecycle hooks', () => { | ||
|
@@ -40,4 +63,30 @@ describe('ReactDOMServerLifecycles', () => { | |
// De-duped | ||
ReactDOMServer.renderToString(<Component />); | ||
}); | ||
|
||
describe('react-lifecycles-compat', () => { | ||
// TODO Replace this with react-lifecycles-compat once it's been published | ||
function polyfill(Component) { | ||
Component.prototype.componentWillMount = function() {}; | ||
Component.prototype.componentWillMount.__suppressDeprecationWarning = true; | ||
Component.prototype.componentWillReceiveProps = function() {}; | ||
Component.prototype.componentWillReceiveProps.__suppressDeprecationWarning = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This pollutes the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice call. |
||
} | ||
|
||
it('should not warn about deprecated cWM/cWRP for polyfilled components', () => { | ||
class PolyfilledComponent extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps() { | ||
return null; | ||
} | ||
render() { | ||
return null; | ||
} | ||
} | ||
|
||
polyfill(PolyfilledComponent); | ||
|
||
ReactDOMServer.renderToString(<PolyfilledComponent />); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,7 @@ describe('ReactDOMServerLifecycles', () => { | |
resetModules(); | ||
}); | ||
|
||
it('should invoke the correct lifecycle hooks', () => { | ||
it('should invoke the correct legacy lifecycle hooks', () => { | ||
const log = []; | ||
|
||
class Outer extends React.Component { | ||
|
@@ -65,6 +65,59 @@ describe('ReactDOMServerLifecycles', () => { | |
]); | ||
}); | ||
|
||
it('should invoke the correct new lifecycle hooks', () => { | ||
const log = []; | ||
|
||
class Outer extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps() { | ||
log.push('outer getDerivedStateFromProps'); | ||
return null; | ||
} | ||
render() { | ||
log.push('outer render'); | ||
return <Inner />; | ||
} | ||
} | ||
|
||
class Inner extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps() { | ||
log.push('inner getDerivedStateFromProps'); | ||
return null; | ||
} | ||
render() { | ||
log.push('inner render'); | ||
return null; | ||
} | ||
} | ||
|
||
ReactDOMServer.renderToString(<Outer />); | ||
expect(log).toEqual([ | ||
'outer getDerivedStateFromProps', | ||
'outer render', | ||
'inner getDerivedStateFromProps', | ||
'inner render', | ||
]); | ||
}); | ||
|
||
it('should not invoke unsafe cWM if static gDSFP is present', () => { | ||
class Component extends React.Component { | ||
state = {}; | ||
static getDerivedStateFromProps() { | ||
return null; | ||
} | ||
UNSAFE_componentWillMount() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want to have a similar test for non-unsafe naming too? |
||
throw Error('unexpected'); | ||
} | ||
render() { | ||
return null; | ||
} | ||
} | ||
|
||
ReactDOMServer.renderToString(<Component />); | ||
}); | ||
|
||
it('should update instance.state with value returned from getDerivedStateFromProps', () => { | ||
class Grandparent extends React.Component { | ||
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.
This should also say "deprecated cWM/cWRP" rather than "unsafe lifecycles"?
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.
Technically this test is verifying that the polyfill can add the old, deprecated methods (not the new ones with the UNSAFE_ prefix) without triggering a deprecation warning. This only really matters in the window between when we turn on the deprecation warnings and when 17 is released and those old methods go away entirely.