Skip to content

Commit f8532df

Browse files
theKasheygregberge
authored andcommitted
fix: double proxy registration (#915)
Could break all the things in case of code splitting - something "old" could be "registered" by a new name (it is just a variable name), obtain a new proxy and unmount old instances. Was fixed in v3. We broke it during migration, and had no tests around it. Fix #912
1 parent b9f801e commit f8532df

7 files changed

+86
-15
lines changed

src/proxy/createClassProxy.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ import { inject, checkLifeCycleMethods, mergeComponents } from './inject'
1919

2020
const has = Object.prototype.hasOwnProperty
2121

22-
const proxies = new WeakMap()
22+
let proxies = new WeakMap()
23+
24+
export const resetClassProxies = () => {
25+
proxies = new WeakMap()
26+
}
2327

2428
const blackListedClassMembers = [
2529
'constructor',
@@ -174,6 +178,7 @@ function createClassProxy(InitialComponent, proxyKey, options) {
174178

175179
let ProxyFacade
176180
let ProxyComponent = null
181+
let proxy
177182

178183
if (!isFunctionalComponent) {
179184
ProxyComponent = proxyClassCreator(InitialComponent, postConstructionAction)
@@ -255,10 +260,11 @@ function createClassProxy(InitialComponent, proxyKey, options) {
255260
// Prevent proxy cycles
256261
const existingProxy = proxies.get(NextComponent)
257262
if (existingProxy) {
258-
update(existingProxy[UNWRAP_PROXY]())
259263
return
260264
}
261265

266+
proxies.set(NextComponent, proxy)
267+
262268
isFunctionalComponent = !isReactClass(NextComponent)
263269
proxyGeneration++
264270

@@ -309,7 +315,9 @@ function createClassProxy(InitialComponent, proxyKey, options) {
309315

310316
update(InitialComponent)
311317

312-
const proxy = { get, update }
318+
proxy = { get, update }
319+
320+
proxies.set(InitialComponent, proxy)
313321
proxies.set(ProxyFacade, proxy)
314322

315323
safeDefineProperty(proxy, UNWRAP_PROXY, {

src/reconciler/proxies.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import createProxy from '../proxy'
2+
import { resetClassProxies } from '../proxy/createClassProxy'
23

34
let proxiesByID
45
let idsByType
@@ -35,6 +36,7 @@ export const createProxyForType = type =>
3536
export const resetProxies = () => {
3637
proxiesByID = {}
3738
idsByType = new WeakMap()
39+
resetClassProxies()
3840
}
3941

4042
resetProxies()

test/AppContainer.dev.test.js

+49
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,55 @@ describe(`AppContainer (dev)`, () => {
12021202
expect(wrapper.text()).toBe('TESTnew childnew child')
12031203
})
12041204

1205+
it('renders children with chunked re-register', () => {
1206+
const spy = jest.fn()
1207+
1208+
class App extends Component {
1209+
componentWillUnmount() {
1210+
spy()
1211+
}
1212+
1213+
render() {
1214+
return <div>I AM CHILD</div>
1215+
}
1216+
}
1217+
1218+
RHL.reset()
1219+
RHL.register(App, 'App1', 'test.js')
1220+
RHL.register(App, 'App2', 'test.js')
1221+
1222+
const wrapper = mount(
1223+
<AppContainer>
1224+
<App />
1225+
</AppContainer>,
1226+
)
1227+
expect(spy).not.toHaveBeenCalled()
1228+
wrapper.setProps({ children: <App /> })
1229+
expect(spy).not.toHaveBeenCalled()
1230+
RHL.register(App, 'App3', 'test.js')
1231+
wrapper.setProps({ children: <App /> })
1232+
expect(spy).not.toHaveBeenCalled()
1233+
1234+
{
1235+
class App extends Component {
1236+
componentWillUnmount() {
1237+
spy()
1238+
}
1239+
1240+
render() {
1241+
return <div>I AM NEW CHILD</div>
1242+
}
1243+
}
1244+
RHL.register(App, 'App3', 'test.js')
1245+
wrapper.setProps({ children: <App /> })
1246+
expect(spy).not.toHaveBeenCalled()
1247+
1248+
RHL.register(App, 'App4', 'test.js')
1249+
wrapper.setProps({ children: <App /> })
1250+
expect(spy).not.toHaveBeenCalled()
1251+
}
1252+
})
1253+
12051254
describe('with HOC-wrapped root', () => {
12061255
it('renders children', () => {
12071256
const spy = jest.fn()

test/proxy/consistency.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ describe('consistency', () => {
149149
expect(proxy.get()).toBe(Proxy)
150150
})
151151

152+
it('prevents double proxy creation', () => {
153+
const proxy1 = createProxy(Bar)
154+
const proxy2 = createProxy(Bar)
155+
expect(proxy1.get()).toBe(proxy2.get())
156+
})
157+
152158
it('prevents mutually recursive proxy cycle', () => {
153159
const barProxy = createProxy(Bar)
154160
const BarProxy = barProxy.get()

test/proxy/instance-property.test.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react'
33
import { ensureNoWarnings, createMounter } from './helper'
44
import createProxy from '../../src/proxy'
55

6-
const fixtures = {
6+
const fixtures = () => ({
77
modern: {
88
InstanceProperty: class InstanceProperty extends React.Component {
99
answer = 42
@@ -79,7 +79,7 @@ const fixtures = {
7979
}
8080
},
8181
},
82-
}
82+
})
8383

8484
describe('instance property', () => {
8585
ensureNoWarnings()
@@ -91,7 +91,7 @@ describe('instance property', () => {
9191
InstanceProperty,
9292
InstancePropertyUpdate,
9393
InstancePropertyRemoval,
94-
} = fixtures[type]
94+
} = fixtures()[type]
9595

9696
it('is available on proxy class instance', () => {
9797
const proxy = createProxy(InstanceProperty)
@@ -158,7 +158,7 @@ describe('instance property', () => {
158158
// })
159159

160160
it('show use the underlayer top value', () => {
161-
const proxy = createProxy(fixtures.modern.InstancePropertyFromContext)
161+
const proxy = createProxy(fixtures().modern.InstancePropertyFromContext)
162162
const Proxy = proxy.get()
163163
const wrapper = mount(<Proxy />)
164164
expect(wrapper.text()).toBe('42')

test/proxy/static-method.test.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
import React from 'react'
33
import { ensureNoWarnings, createMounter } from './helper'
44
import createProxy from '../../src/proxy'
5+
import reactHotLoader from '../../src/reactHotLoader'
56

6-
const fixtures = {
7+
const fixtures = () => ({
78
modern: {
89
StaticMethod: class StaticMethod extends React.Component {
910
static getAnswer() {
@@ -31,19 +32,21 @@ const fixtures = {
3132
}
3233
},
3334
},
34-
}
35+
})
3536

3637
describe('static method', () => {
3738
ensureNoWarnings()
3839
const { mount } = createMounter()
3940

40-
Object.keys(fixtures).forEach(type => {
41+
Object.keys(fixtures()).forEach(type => {
4142
describe(type, () => {
4243
const {
4344
StaticMethod,
4445
StaticMethodUpdate,
4546
StaticMethodRemoval,
46-
} = fixtures[type]
47+
} = fixtures()[type]
48+
49+
beforeEach(() => reactHotLoader.reset())
4750

4851
it('is available on proxy class instance', () => {
4952
const proxy = createProxy(StaticMethod)

test/proxy/static-property.test.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import React from 'react'
44
import PropTypes from 'prop-types'
55
import { ensureNoWarnings, createMounter } from './helper'
66
import createProxy from '../../src/proxy'
7+
import reactHotLoader from '../../src/reactHotLoader'
78

8-
const fixtures = {
9+
const fixtures = () => ({
910
modern: {
1011
StaticProperty: class StaticProperty extends React.Component {
1112
static answer = 42
@@ -65,21 +66,23 @@ const fixtures = {
6566
}
6667
},
6768
},
68-
}
69+
})
6970

7071
describe('static property', () => {
7172
ensureNoWarnings()
7273
const { mount } = createMounter()
7374

74-
Object.keys(fixtures).forEach(type => {
75+
Object.keys(fixtures()).forEach(type => {
7576
describe(type, () => {
7677
const {
7778
StaticProperty,
7879
StaticPropertyUpdate,
7980
StaticPropertyRemoval,
8081
WithPropTypes,
8182
WithPropTypesUpdate,
82-
} = fixtures[type]
83+
} = fixtures()[type]
84+
85+
beforeEach(() => reactHotLoader.reset())
8386

8487
it('is available on proxy class instance', () => {
8588
const proxy = createProxy(StaticProperty)

0 commit comments

Comments
 (0)