Skip to content

Commit

Permalink
remove componentWillReceiveProps from proxies #918
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Apr 6, 2018
1 parent 36e8ac7 commit a5a7c93
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 53 deletions.
2 changes: 1 addition & 1 deletion examples/styled-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-emotion": "^8.0.12",
"react-hot-loader": "next",
"react-hot-loader": "^4.0.1",
"styled-components": "^2.4.0"
}
}
15 changes: 5 additions & 10 deletions src/AppContainer.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,15 @@ class AppContainer extends React.Component {
}
}

componentWillReceiveProps() {
if (this.state.generation !== getGeneration()) {
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.generation !== getGeneration()) {
// Hot reload is happening.

this.setState({
return {
error: null,
generation: getGeneration(),
})

// perform sandboxed render to find similarities between new and old code
renderReconciler(this, true)
// it is possible to flush update out of render cycle
flushScheduledUpdates()
}
}
return null
}

shouldComponentUpdate(prevProps, prevState) {
Expand Down
12 changes: 6 additions & 6 deletions src/proxy/createClassProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ const blackListedClassMembers = [
]

const defaultRenderOptions = {
componentWillReceiveProps: identity,
componentWillRender: identity,
componentDidUpdate: result => result,
componentDidRender: result => result,
}

Expand Down Expand Up @@ -129,9 +129,9 @@ function createClassProxy(InitialComponent, proxyKey, options) {
target[PROXY_IS_MOUNTED] = true
},
)
const componentWillReceiveProps = lifeCycleWrapperFactory(
'componentWillReceiveProps',
renderOptions.componentWillReceiveProps,
const componentDidUpdate = lifeCycleWrapperFactory(
'componentDidUpdate',
renderOptions.componentDidUpdate,
)
const componentWillUnmount = lifeCycleWrapperFactory(
'componentWillUnmount',
Expand All @@ -157,7 +157,7 @@ function createClassProxy(InitialComponent, proxyKey, options) {
result = CurrentComponent.prototype.render.call(this)
}

return renderOptions.componentDidRender(result)
return renderOptions.componentDidRender.call(this, result)
}

function proxiedRender() {
Expand All @@ -171,7 +171,7 @@ function createClassProxy(InitialComponent, proxyKey, options) {
render: proxiedRender,
hotComponentRender,
componentDidMount,
componentWillReceiveProps,
componentDidUpdate,
componentWillUnmount,
})
}
Expand Down
4 changes: 4 additions & 0 deletions src/reconciler/hotReplacementRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ export const flushScheduledUpdates = () => {
)
}

export const unscheduleUpdate = instance => {
scheduledUpdates = scheduledUpdates.filter(inst => inst !== instance)
}

const scheduleInstanceUpdate = instance => {
scheduledUpdates.push(instance)
if (!scheduledUpdate) {
Expand Down
5 changes: 3 additions & 2 deletions src/reconciler/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import getReactStack from '../internal/getReactStack'
import hotReplacementRender, {
flushScheduledUpdates
flushScheduledUpdates,
unscheduleUpdate
} from './hotReplacementRender'

const reconcileHotReplacement = ReactInstance =>
hotReplacementRender(ReactInstance, getReactStack(ReactInstance))

export { flushScheduledUpdates }
export { flushScheduledUpdates, unscheduleUpdate }

export default reconcileHotReplacement
17 changes: 9 additions & 8 deletions src/reconciler/proxyAdapter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import reactHotLoader from '../reactHotLoader'
import { get as getGeneration } from '../global/generation'
import { getProxyByType, setStandInOptions } from './proxies'
import reconcileHotReplacement, { flushScheduledUpdates } from './index'
import reconcileHotReplacement, {
flushScheduledUpdates,
unscheduleUpdate,
} from './index'

export const RENDERED_GENERATION = 'REACT_HOT_LOADER_RENDERED_GENERATION'

Expand All @@ -28,14 +31,12 @@ function asyncReconciledRender(target) {
renderReconciler(target, false)
}

function syncReconciledRender(target) {
if (renderReconciler(target, false)) {
flushScheduledUpdates()
export function proxyWrapper(element) {
// post wrap on post render
if (!reactHotLoader.disableProxyCreation) {
unscheduleUpdate(this)
}
}

export const proxyWrapper = element => {
// post wrap on post render
if (!element) {
return element
}
Expand All @@ -55,7 +56,7 @@ export const proxyWrapper = element => {
}

setStandInOptions({
componentWillReceiveProps: syncReconciledRender,
componentWillRender: asyncReconciledRender,
componentDidRender: proxyWrapper,
componentDidUpdate: flushScheduledUpdates,
})
16 changes: 10 additions & 6 deletions test/AppContainer.dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe(`AppContainer (dev)`, () => {

it('force updates the tree on receiving new children', () => {
const spy = jest.fn()
const spy2 = jest.fn()

class App extends Component {
shouldComponentUpdate() {
Expand Down Expand Up @@ -66,6 +67,7 @@ describe(`AppContainer (dev)`, () => {

render() {
spy()
spy2()
return <div>ho</div>
}
}
Expand All @@ -75,7 +77,9 @@ describe(`AppContainer (dev)`, () => {
}

expect(spy).toHaveBeenCalledTimes(3)
expect(wrapper.contains(<div>ho</div>)).toBe(true)
expect(spy2).toHaveBeenCalledTimes(2)

expect(wrapper.update().contains(<div>ho</div>)).toBe(true)
})

it('force updates the tree on receiving cached children', () => {
Expand Down Expand Up @@ -115,7 +119,7 @@ describe(`AppContainer (dev)`, () => {
}

expect(spy).toHaveBeenCalledTimes(3)
expect(wrapper.contains(<div>ho</div>)).toBe(true)
expect(wrapper.update().contains(<div>ho</div>)).toBe(true)
})

it('renders latest children on receiving cached never-rendered children', () => {
Expand Down Expand Up @@ -653,7 +657,7 @@ describe(`AppContainer (dev)`, () => {
}

expect(spy).toHaveBeenCalledTimes(1 + 2)
expect(wrapper.contains(<div>ho</div>)).toBe(true)
expect(wrapper.update().contains(<div>ho</div>)).toBe(true)
})

it('force updates the tree on receiving cached children', () => {
Expand Down Expand Up @@ -691,7 +695,7 @@ describe(`AppContainer (dev)`, () => {
}

expect(spy).toHaveBeenCalledTimes(1 + 2)
expect(wrapper.contains(<div>ho</div>)).toBe(true)
expect(wrapper.update().contains(<div>ho</div>)).toBe(true)
})

it('renders latest children on receiving cached never-rendered children', () => {
Expand Down Expand Up @@ -830,7 +834,7 @@ describe(`AppContainer (dev)`, () => {
}

expect(spy).toHaveBeenCalledTimes(1 + 2)
expect(wrapper.contains(<div>ho</div>)).toBe(true)
expect(wrapper.update().contains(<div>ho</div>)).toBe(true)
})

it('force updates the tree on receiving cached children', () => {
Expand Down Expand Up @@ -869,7 +873,7 @@ describe(`AppContainer (dev)`, () => {
}

expect(spy).toHaveBeenCalledTimes(1 + 2)
expect(wrapper.contains(<div>ho</div>)).toBe(true)
expect(wrapper.update().contains(<div>ho</div>)).toBe(true)
})

it('renders latest children on receiving cached never-rendered children', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/proxy/consistency.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ describe('consistency', () => {
'render',
'hotComponentRender',
'componentDidMount',
'componentWillReceiveProps',
'componentDidUpdate',
'componentWillUnmount',
])
})
Expand Down
15 changes: 11 additions & 4 deletions test/proxy/lifecycle-method.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('lifecycle method', () => {
}

render() {
return <div>{this.superSecret * 2}</div>
return <div>!{this.superSecret * 5}</div>
}
}
return { App1, App2 }
Expand All @@ -63,7 +63,7 @@ describe('lifecycle method', () => {
return testFabric(methodName)(Component, patchedRender, spy)
}

it('handle componentWillMount', () => {
it('handle componentWillMount', done => {
const spy = jest.fn()
const { App1, App2 } = getTestClass('componentWillMount', spy)

Expand All @@ -78,7 +78,11 @@ describe('lifecycle method', () => {
proxy.update(App2)
wrapper.instance().forceUpdate()
expect(spy).toHaveBeenCalledTimes(1)
expect(wrapper.text()).toContain('PATCHED + 6')
// first render before hot render
expect(wrapper.text()).toContain('PATCHED + !10')
wrapper.instance().forceUpdate()
expect(wrapper.text()).toContain('PATCHED + !15')
done()
})

it('handle componentDidMount', () => {
Expand All @@ -96,6 +100,9 @@ describe('lifecycle method', () => {
proxy.update(App2)
wrapper.instance().forceUpdate()
expect(spy).toHaveBeenCalledTimes(1)
expect(wrapper.text()).toContain('PATCHED + 6')
// first render before hot render
expect(wrapper.text()).toContain('PATCHED + !10')
wrapper.instance().forceUpdate()
expect(wrapper.text()).toContain('PATCHED + !15')
})
})
32 changes: 17 additions & 15 deletions test/reconciler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe('reconciler', () => {
incrementGeneration()
wrapper.setProps({ update: 'now' })

expect(wrapper.find(<third.Component />.type).length).toBe(1)
expect(wrapper.update().find(<third.Component />.type).length).toBe(1)
// first will never be unmounted
expect(first.unmounted).toHaveBeenCalledTimes(0)
expect(second.unmounted).toHaveBeenCalledTimes(1)
Expand Down Expand Up @@ -231,7 +231,7 @@ describe('reconciler', () => {

incrementGeneration()
wrapper.setProps({ second: false })
expect(First.rendered).toHaveBeenCalledTimes(6)
expect(First.rendered).toHaveBeenCalledTimes(5)
expect(Second.rendered).toHaveBeenCalledTimes(3)

expect(First.unmounted).toHaveBeenCalledTimes(0)
Expand Down Expand Up @@ -265,29 +265,29 @@ describe('reconciler', () => {

incrementGeneration()
wrapper.setProps({ update: 'now' })
expect(First.rendered).toHaveBeenCalledTimes(4)
expect(Second.rendered).toHaveBeenCalledTimes(4)
expect(First.rendered).toHaveBeenCalledTimes(3)
expect(Second.rendered).toHaveBeenCalledTimes(3)

incrementGeneration()
wrapper.setProps({ second: false })
expect(First.rendered).toHaveBeenCalledTimes(7)
expect(Second.rendered).toHaveBeenCalledTimes(4)
expect(First.rendered).toHaveBeenCalledTimes(5)
expect(Second.rendered).toHaveBeenCalledTimes(3)

expect(First.unmounted).toHaveBeenCalledTimes(0)
expect(Second.unmounted).toHaveBeenCalledTimes(1)
})

it('should handle child mounting', () => {
const First = spyComponent(() => <u>1</u>, 'test', '1')
const Second = spyComponent(() => <u>2</u>, 'test', '2')
const Third = spyComponent(() => <u>2</u>, 'test', '2')
const First = spyComponent(() => <u>test1</u>, 'test1', '1')
const Second = spyComponent(() => <u>test2</u>, 'test2', '2')
const Third = spyComponent(() => <u>test3</u>, 'test3', '3')
const App = ({ first, second, third }) => (
<div>
{first && <First.Component />}
{second && [
<div key="1" />,
<div key="1">start</div>,
<Second.Component key="2" />,
<div key="3" />,
<div key="3">middle</div>,
third && <Third.Component key="4" />,
]}
</div>
Expand All @@ -304,13 +304,15 @@ describe('reconciler', () => {

incrementGeneration()
wrapper.setProps({ second: true })
expect(First.rendered).toHaveBeenCalledTimes(4) // +3 (reconcile + update + render)
expect(Second.rendered).toHaveBeenCalledTimes(2) // (update from first + render)
expect(First.rendered).toHaveBeenCalledTimes(3) // +3 (reconcile + update + render)
expect(Second.rendered).toHaveBeenCalledTimes(1) // (update from first + render)

wrapper.setProps({ third: true })
expect(First.rendered).toHaveBeenCalledTimes(5)
expect(Second.rendered).toHaveBeenCalledTimes(3)
expect(First.rendered).toHaveBeenCalledTimes(4)
expect(Second.rendered).toHaveBeenCalledTimes(2)
expect(Third.rendered).toHaveBeenCalledTimes(1)

expect(wrapper.update().html()).toMatch(/test3/)
})

it('should handle function as a child', () => {
Expand Down
15 changes: 15 additions & 0 deletions test/reconciler/proxyAdapter.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/* eslint-env browser */
import { proxyWrapper } from '../../src/reconciler/proxyAdapter'
import * as proxies from '../../src/reconciler/proxies'
import reactHotLoader from '../../src/reactHotLoader'
import { unscheduleUpdate } from '../../src/reconciler/hotReplacementRender'

jest.mock('../../src/reconciler/proxies')
jest.mock('../../src/reconciler/hotReplacementRender')

proxies.getProxyByType.mockReturnValue({ get: () => 'proxy' })

Expand All @@ -27,4 +30,16 @@ describe(`proxyAdapter`, () => {
prop: 42,
})
})

it('should remove rendered proxy', () => {
const object = {}
unscheduleUpdate.mockClear()
reactHotLoader.disableProxyCreation = 1
proxyWrapper.call(object)
expect(unscheduleUpdate).not.toHaveBeenCalled()

reactHotLoader.disableProxyCreation = 0
proxyWrapper.call(object)
expect(unscheduleUpdate).toHaveBeenCalledWith(object)
})
})

0 comments on commit a5a7c93

Please sign in to comment.