Skip to content

Commit 95a9e79

Browse files
committed
fix(react-hot-loader): fix componentWillUpdate
Pass props and state Also add a test Thanks #744 (@katmai7) for reporting problem
1 parent 4cd081b commit 95a9e79

File tree

2 files changed

+81
-70
lines changed

2 files changed

+81
-70
lines changed

Diff for: packages/react-hot-loader/src/reconciler/hotReplacementRender.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,6 @@ const render = component => {
9797
return []
9898
}
9999
if (isReactClass(component)) {
100-
if (component.componentWillUpdate) {
101-
// force-refresh component (bypass redux renderedComponent)
102-
component.componentWillUpdate()
103-
}
104100
return component.render()
105101
}
106102
if (isArray(component)) {
@@ -170,13 +166,17 @@ const hotReplacementRender = (instance, stack) => {
170166
const next = instance => {
171167
// copy over props as long new component may be hidden inside them
172168
// child does not have all props, as long some of them can be calculated on componentMount.
173-
const props = { ...instance.props }
169+
const nextProps = { ...instance.props }
174170
for (const key in child.props) {
175171
if (child.props[key]) {
176-
props[key] = child.props[key]
172+
nextProps[key] = child.props[key]
177173
}
178174
}
179-
instance.props = props
175+
if (isReactClass(instance) && instance.componentWillUpdate) {
176+
// Force-refresh component (bypass redux renderedComponent)
177+
instance.componentWillUpdate(nextProps, instance.state)
178+
}
179+
instance.props = nextProps
180180
hotReplacementRender(instance, stackChild)
181181
}
182182

Diff for: packages/react-hot-loader/test/reconciler.test.js

+74-63
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
/* eslint-env jest */
22

33
import React, { Component } from 'react'
4-
import Adapter from 'enzyme-adapter-react-16'
5-
import { mount, configure } from 'enzyme'
4+
import { mount } from 'enzyme'
65
import '../src/patch.dev'
76
import AppContainer from '../src/AppContainer.dev'
87
import { didUpdate } from '../src/updateCounter'
98
import getReactStack from '../src/internal/getReactStack'
109
import { areComponentsEqual } from '../src/utils.dev'
1110
import RHL from '../src/reactHotLoader'
1211

13-
configure({ adapter: new Adapter() })
14-
15-
const createTestDouble = (render, name, key) => {
12+
const spyComponent = (render, displayName, key) => {
1613
const mounted = jest.fn()
1714
const unmounted = jest.fn()
15+
const willUpdate = jest.fn()
1816

1917
class TestingComponent extends Component {
2018
componentWillMount() {
2119
mounted()
2220
}
2321

22+
componentWillUpdate(nextProps, nextState) {
23+
willUpdate(nextProps, nextState, this.props, this.state)
24+
}
25+
2426
componentWillUnmount() {
2527
unmounted()
2628
}
@@ -36,11 +38,13 @@ const createTestDouble = (render, name, key) => {
3638
}
3739
}
3840

39-
if (name) {
40-
TestingComponent.displayName = name
41+
if (displayName) {
42+
TestingComponent.displayName = displayName
4143
}
44+
4245
return {
4346
Component: TestingComponent,
47+
willUpdate,
4448
mounted,
4549
unmounted,
4650
key,
@@ -50,38 +54,36 @@ const createTestDouble = (render, name, key) => {
5054
describe('reconciler', () => {
5155
describe('Application', () => {
5256
it('should regenerate internal component', () => {
53-
const ComponentRoot = createTestDouble(
57+
const root = spyComponent(
5458
({ children }) => <div>{children}</div>,
5559
'root',
5660
'root',
5761
)
5862

59-
const Component1 = createTestDouble(
63+
const first = spyComponent(
6064
({ children }) => <b>{children}</b>,
6165
'test',
6266
'1',
6367
)
64-
const Component2 = createTestDouble(() => <u>REPLACED</u>, 'test', '2')
65-
const Component3 = createTestDouble(
66-
() => <u>NEW ONE</u>,
67-
'somethingElse',
68-
'3',
69-
)
68+
const second = spyComponent(() => <u>REPLACED</u>, 'test', '2')
69+
const third = spyComponent(() => <u>NEW ONE</u>, 'somethingElse', '3')
70+
71+
let currentComponent = first
72+
const currentProps = {}
7073

71-
let currentComponent = Component1
7274
const ComponentSwap = props => {
7375
const { Component } = currentComponent
7476
return (
7577
<blockquote>
76-
<Component {...props} />
78+
<Component {...props} {...currentProps} />
7779
</blockquote>
7880
)
7981
}
8082

8183
const App = () => (
82-
<ComponentRoot.Component>
84+
<root.Component>
8385
<ComponentSwap>42</ComponentSwap>
84-
</ComponentRoot.Component>
86+
</root.Component>
8587
)
8688

8789
const wrapper = mount(
@@ -91,56 +93,65 @@ describe('reconciler', () => {
9193
)
9294

9395
// mount and perform first checks
94-
expect(wrapper.find(<Component1.Component />.type).length).toBe(1)
95-
expect(ComponentRoot.mounted).toHaveBeenCalledTimes(1)
96-
expect(Component1.mounted).toHaveBeenCalledTimes(1)
96+
expect(wrapper.find(<first.Component />.type).length).toBe(1)
97+
expect(root.mounted).toHaveBeenCalledTimes(1)
98+
expect(first.mounted).toHaveBeenCalledTimes(1)
9799

98100
// replace with `the same` component
99-
currentComponent = Component2
101+
currentComponent = second
100102
// they are different
101-
expect(
102-
areComponentsEqual(Component1.Component, Component2.Component),
103-
).toBe(false)
103+
expect(areComponentsEqual(first.Component, second.Component)).toBe(false)
104+
105+
currentProps.newProp = true
104106
didUpdate()
105107
wrapper.setProps({ update: 'now' })
106108
// not react-stand-in merge them together
107-
expect(
108-
areComponentsEqual(Component1.Component, Component2.Component),
109-
).toBe(true)
110-
expect(wrapper.find(<Component1.Component />.type).length).toBe(1)
111-
expect(wrapper.find(<Component2.Component />.type).length).toBe(1)
112-
113-
expect(ComponentRoot.mounted).toHaveBeenCalledTimes(1)
114-
expect(Component1.unmounted).toHaveBeenCalledTimes(0)
115-
expect(Component2.mounted).toHaveBeenCalledTimes(0)
109+
expect(areComponentsEqual(first.Component, second.Component)).toBe(true)
110+
expect(wrapper.find(<first.Component />.type).length).toBe(1)
111+
expect(wrapper.find(<second.Component />.type).length).toBe(1)
112+
113+
expect(root.mounted).toHaveBeenCalledTimes(1)
114+
expect(first.unmounted).toHaveBeenCalledTimes(0)
115+
expect(second.mounted).toHaveBeenCalledTimes(0)
116+
expect(second.willUpdate).toHaveBeenCalledTimes(2)
117+
expect(second.willUpdate.mock.calls[0]).toEqual([
118+
{ children: '42' },
119+
null,
120+
{ children: '42' },
121+
null,
122+
])
123+
expect(second.willUpdate.mock.calls[1]).toEqual([
124+
{ children: '42', newProp: true },
125+
null,
126+
{ children: '42' },
127+
null,
128+
])
116129

117130
// replace with a different component
118-
currentComponent = Component3
131+
currentComponent = third
119132
didUpdate()
120133
wrapper.setProps({ update: 'now' })
121-
expect(wrapper.find(<Component3.Component />.type).length).toBe(1)
122-
// Component1 will never be unmounted
123-
expect(Component1.unmounted).toHaveBeenCalledTimes(0)
124-
expect(Component2.unmounted).toHaveBeenCalledTimes(1)
125-
expect(Component3.mounted).toHaveBeenCalledTimes(1)
126-
127-
expect(
128-
areComponentsEqual(Component1.Component, Component3.Component),
129-
).toBe(false)
130-
expect(
131-
areComponentsEqual(Component2.Component, Component3.Component),
132-
).toBe(false)
134+
expect(wrapper.find(<third.Component />.type).length).toBe(1)
135+
// first will never be unmounted
136+
expect(first.unmounted).toHaveBeenCalledTimes(0)
137+
expect(second.unmounted).toHaveBeenCalledTimes(1)
138+
expect(third.mounted).toHaveBeenCalledTimes(1)
139+
140+
expect(areComponentsEqual(first.Component, third.Component)).toBe(false)
141+
expect(areComponentsEqual(second.Component, third.Component)).toBe(false)
133142
})
134143

135144
it('should regenerate internal component', () => {
136-
const Component1 = createTestDouble(
145+
const first = spyComponent(
137146
({ children }) => <b>{children}</b>,
138147
'test',
139148
'1',
140149
)
141-
const Component2 = createTestDouble(() => <u>REPLACED</u>, 'test', '2')
142150

143-
let currentComponent = Component1
151+
const second = spyComponent(() => <u>REPLACED</u>, 'test', '2')
152+
153+
let currentComponent = first
154+
144155
const ComponentSwap = props => {
145156
const { Component } = currentComponent
146157
return (
@@ -160,32 +171,32 @@ describe('reconciler', () => {
160171
)
161172

162173
const wrapper = mount(
163-
<AppContainer reconciler>
174+
<AppContainer>
164175
<App />
165176
</AppContainer>,
166177
)
167178

168-
currentComponent = Component2
179+
currentComponent = second
169180
didUpdate()
170181
wrapper.setProps({ update: 'now' })
171182

172-
expect(Component1.unmounted).toHaveBeenCalledTimes(0)
173-
expect(Component2.mounted).toHaveBeenCalledTimes(0)
183+
expect(first.unmounted).toHaveBeenCalledTimes(0)
184+
expect(second.mounted).toHaveBeenCalledTimes(0)
174185
})
175186

176187
it('should handle function as a child', () => {
177-
const Component1 = createTestDouble(
188+
const first = spyComponent(
178189
({ children }) => <b>{children(0)}</b>,
179190
'test',
180191
'1',
181192
)
182-
const Component2 = createTestDouble(
193+
const second = spyComponent(
183194
({ children }) => <u>{children(1)}</u>,
184195
'test',
185196
'2',
186197
)
187198

188-
let currentComponent = Component1
199+
let currentComponent = first
189200
const ComponentSwap = props => {
190201
const { Component } = currentComponent
191202
return (
@@ -203,19 +214,19 @@ describe('reconciler', () => {
203214
)
204215

205216
const wrapper = mount(
206-
<AppContainer reconciler>
217+
<AppContainer>
207218
<App />
208219
</AppContainer>,
209220
)
210221

211222
expect(wrapper.text()).toContain(42)
212223

213-
currentComponent = Component2
224+
currentComponent = second
214225
didUpdate()
215226
wrapper.setProps({ update: 'now' })
216227

217-
expect(Component1.unmounted).toHaveBeenCalledTimes(0)
218-
expect(Component2.mounted).toHaveBeenCalledTimes(0)
228+
expect(first.unmounted).toHaveBeenCalledTimes(0)
229+
expect(second.mounted).toHaveBeenCalledTimes(0)
219230
expect(wrapper.text()).toContain(43)
220231
})
221232
})
@@ -227,7 +238,7 @@ describe('reconciler', () => {
227238

228239
RHL.disableComponentProxy = true
229240
const wrapper = mount(
230-
<AppContainer reconciler>
241+
<AppContainer>
231242
<div>
232243
<div>
233244
<Transform>

0 commit comments

Comments
 (0)