Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit e995a1a

Browse files
linusthe3rdFezVrasta
authored andcommitted
fix: referenceNode memory leak in Manager (#312) (#313)
* fix: referenceNode memory leak Instead of storing referenceNode in state, store it in a class property. This is done to enable the component to cleanup referenceNode’s value during unmounting. I also added unit tests to account for this change, but that resulted in me having to update some dependencies and snapshots to get unit tests to pass. * CR: Create separate setter and getter contexts In order to help reduce re-renders of the Reference component, this commit creates separate context objects for the setters and getters of the referenceNode attribute. This also removes the need for a context object, so it simplifies the management of the referenceNode attribute in the Manager class. * CR Feedback: remove need for componentWillUnmount * FIx flow * Update size snapshot
1 parent 9201c9b commit e995a1a

File tree

6 files changed

+97
-92
lines changed

6 files changed

+97
-92
lines changed

.size-snapshot.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
{
22
"dist/index.umd.js": {
3-
"bundled": 67132,
4-
"minified": 23802,
5-
"gzipped": 7191
3+
"bundled": 67000,
4+
"minified": 23875,
5+
"gzipped": 7202
66
},
77
"dist/index.umd.min.js": {
8-
"bundled": 31768,
9-
"minified": 12924,
10-
"gzipped": 4205
8+
"bundled": 31451,
9+
"minified": 12925,
10+
"gzipped": 4201
1111
},
1212
"dist/index.esm.js": {
13-
"bundled": 12577,
14-
"minified": 7364,
15-
"gzipped": 2096,
13+
"bundled": 12172,
14+
"minified": 7049,
15+
"gzipped": 2064,
1616
"treeshaked": {
1717
"rollup": {
18-
"code": 3832,
18+
"code": 3742,
1919
"import_statements": 137
2020
},
2121
"webpack": {
22-
"code": 4938
22+
"code": 4872
2323
}
2424
}
2525
}

src/Manager.js

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,32 @@
22
import * as React from 'react';
33
import createContext, { type Context } from 'create-react-context';
44

5-
export const ManagerContext: Context<{
6-
setReferenceNode?: (?HTMLElement) => void,
7-
referenceNode?: ?HTMLElement,
8-
}> = createContext({ setReferenceNode: undefined, referenceNode: undefined });
5+
export const ManagerReferenceNodeContext: Context<?HTMLElement> = createContext();
6+
export const ManagerReferenceNodeSetterContext: Context<void | (?HTMLElement) => void> = createContext();
97

108
export type ManagerProps = {
119
children: React.Node,
1210
};
13-
type ManagerState = {
14-
context: {
15-
setReferenceNode?: (?HTMLElement) => void,
16-
referenceNode?: ?HTMLElement,
17-
},
18-
};
1911

20-
export default class Manager extends React.Component<
21-
ManagerProps,
22-
ManagerState
23-
> {
24-
constructor() {
25-
super();
26-
this.state = {
27-
context: {
28-
setReferenceNode: this.setReferenceNode,
29-
referenceNode: undefined,
30-
},
31-
};
32-
}
12+
export default class Manager extends React.Component<ManagerProps> {
13+
referenceNode: ?HTMLElement;
3314

34-
setReferenceNode = (referenceNode: ?HTMLElement) => {
35-
if (!referenceNode || this.state.context.referenceNode === referenceNode) {
36-
return;
15+
setReferenceNode = (newReferenceNode: ?HTMLElement) => {
16+
if (this.referenceNode !== newReferenceNode) {
17+
this.referenceNode = newReferenceNode;
18+
this.forceUpdate();
3719
}
38-
39-
this.setState(({ context }) => ({
40-
context: { ...context, referenceNode },
41-
}));
4220
};
4321

4422
render() {
4523
return (
46-
<ManagerContext.Provider value={this.state.context}>
47-
{this.props.children}
48-
</ManagerContext.Provider>
24+
<ManagerReferenceNodeContext.Provider value={this.referenceNode}>
25+
<ManagerReferenceNodeSetterContext.Provider
26+
value={this.setReferenceNode}
27+
>
28+
{this.props.children}
29+
</ManagerReferenceNodeSetterContext.Provider>
30+
</ManagerReferenceNodeContext.Provider>
4931
);
5032
}
5133
}

src/Manager.test.js

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Manager, Popper, Reference } from '.';
88
import { InnerPopper } from './Popper';
99

1010
// Private API
11-
import { ManagerContext } from './Manager';
11+
import { ManagerReferenceNodeContext, ManagerReferenceNodeSetterContext } from './Manager';
1212

1313
describe('Manager component', () => {
1414
it('renders the expected markup', () => {
@@ -27,14 +27,18 @@ describe('Manager component', () => {
2727

2828
const wrapper = mount(
2929
<Manager>
30-
<ManagerContext.Consumer>
31-
{({ setReferenceNode, referenceNode }) => (
32-
<Reference
33-
setReferenceNode={setReferenceNode}
34-
referenceNode={referenceNode}
35-
/>
30+
<ManagerReferenceNodeSetterContext.Consumer>
31+
{(setReferenceNode) => (
32+
<ManagerReferenceNodeContext.Consumer>
33+
{(referenceNode) => (
34+
<Reference
35+
setReferenceNode={setReferenceNode}
36+
referenceNode={referenceNode}
37+
/>
38+
)}
39+
</ManagerReferenceNodeContext.Consumer>
3640
)}
37-
</ManagerContext.Consumer>
41+
</ManagerReferenceNodeSetterContext.Consumer>
3842
</Manager>
3943
);
4044

@@ -79,21 +83,51 @@ describe('Managed Reference', () => {
7983
const PopperInstance = wrapper.find(InnerPopper);
8084
expect(PopperInstance.prop('referenceElement')).toBe(referenceElement);
8185
});
86+
87+
it('updates the referenceNode if setReferenceNode is called with a new value', () => {
88+
let referenceElement;
89+
let ReferenceComp = ({ innerRef }) => (
90+
<div
91+
ref={node => {
92+
// We just want to invoke this once so that we have access to the referenceElement in the upper scope.
93+
if (referenceElement) return;
94+
innerRef(node);
95+
referenceElement = node;
96+
}}
97+
>
98+
hello
99+
</div>
100+
);
101+
const wrapper = mount(
102+
<Manager>
103+
<Reference>{({ ref }) => <ReferenceComp innerRef={ref} />}</Reference>
104+
<Popper referenceElement={undefined}>{() => null}</Popper>
105+
</Manager>
106+
);
107+
108+
expect(wrapper.instance().referenceNode).toBe(referenceElement);
109+
wrapper.instance().setReferenceNode(null);
110+
expect(wrapper.instance().referenceNode).toBeNull();
111+
});
82112
});
83113

84114
describe('ReferenceNodeContext', () => {
85115
it('provides proper default values', () => {
86116
const Reference = () => null;
87117
const wrapper = mount(
88118
<div>
89-
<ManagerContext.Consumer>
90-
{({ setReferenceNode, referenceNode }) => (
91-
<Reference
92-
setReferenceNode={setReferenceNode}
93-
referenceNode={referenceNode}
94-
/>
119+
<ManagerReferenceNodeSetterContext.Consumer>
120+
{(setReferenceNode) => (
121+
<ManagerReferenceNodeContext.Consumer>
122+
{(referenceNode) => (
123+
<Reference
124+
setReferenceNode={setReferenceNode}
125+
referenceNode={referenceNode}
126+
/>
127+
)}
128+
</ManagerReferenceNodeContext.Consumer>
95129
)}
96-
</ManagerContext.Consumer>
130+
</ManagerReferenceNodeSetterContext.Consumer>
97131
</div>
98132
);
99133

src/Popper.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import PopperJS, {
88
type ReferenceObject,
99
} from 'popper.js';
1010
import type { Style } from 'typed-styles';
11-
import { ManagerContext } from './Manager';
11+
import { ManagerReferenceNodeContext } from './Manager';
1212
import { unwrapArray, setRef, shallowEqual } from './utils';
1313
import { type Ref } from "./RefTypes";
1414

@@ -224,15 +224,15 @@ export { placements };
224224

225225
export default function Popper({ referenceElement, ...props }: PopperProps) {
226226
return (
227-
<ManagerContext.Consumer>
228-
{({ referenceNode }) => (
227+
<ManagerReferenceNodeContext.Consumer>
228+
{(referenceNode) => (
229229
<InnerPopper
230230
referenceElement={
231231
referenceElement !== undefined ? referenceElement : referenceNode
232232
}
233233
{...props}
234234
/>
235235
)}
236-
</ManagerContext.Consumer>
236+
</ManagerReferenceNodeContext.Consumer>
237237
);
238238
}

src/Reference.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22
import * as React from 'react';
33
import warning from 'warning';
4-
import { ManagerContext } from './Manager';
4+
import { ManagerReferenceNodeSetterContext } from './Manager';
55
import { safeInvoke, unwrapArray, setRef } from './utils';
66
import { type Ref } from "./RefTypes";
77

@@ -38,10 +38,10 @@ class InnerReference extends React.Component<
3838

3939
export default function Reference(props: ReferenceProps) {
4040
return (
41-
<ManagerContext.Consumer>
42-
{({ setReferenceNode }) => (
41+
<ManagerReferenceNodeSetterContext.Consumer>
42+
{(setReferenceNode) => (
4343
<InnerReference setReferenceNode={setReferenceNode} {...props} />
4444
)}
45-
</ManagerContext.Consumer>
45+
</ManagerReferenceNodeSetterContext.Consumer>
4646
);
4747
}

src/Reference.test.js

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { mount } from 'enzyme';
77
import { Reference } from '.';
88

99
// Private API
10-
import { ManagerContext } from './Manager';
10+
import { ManagerReferenceNodeSetterContext } from './Manager';
1111

1212
jest.mock('warning');
1313

@@ -22,33 +22,27 @@ describe('Arrow component', () => {
2222
// HACK: wrapping DIV needed to make Enzyme happy for now
2323
const wrapper = mount(
2424
<div>
25-
<ManagerContext.Provider
26-
value={{
27-
setReferenceNode,
28-
referenceNode: undefined,
29-
}}
25+
<ManagerReferenceNodeSetterContext.Provider
26+
value={setReferenceNode}
3027
>
3128
<Reference>{({ ref }) => <div ref={ref} />}</Reference>
32-
</ManagerContext.Provider>
29+
</ManagerReferenceNodeSetterContext.Provider>
3330
</div>
3431
);
3532
expect(wrapper.children()).toMatchSnapshot();
3633
});
3734

38-
it('consumes the ManagerContext from Manager', () => {
35+
it('consumes the ManagerReferenceNodeSetterContext from Manager', () => {
3936
const setReferenceNode = jest.fn();
4037

4138
// HACK: wrapping DIV needed to make Enzyme happy for now
4239
mount(
4340
<div>
44-
<ManagerContext.Provider
45-
value={{
46-
setReferenceNode,
47-
referenceNode: undefined,
48-
}}
41+
<ManagerReferenceNodeSetterContext.Provider
42+
value={setReferenceNode}
4943
>
5044
<Reference>{({ ref }) => <div ref={ref} />}</Reference>
51-
</ManagerContext.Provider>
45+
</ManagerReferenceNodeSetterContext.Provider>
5246
</div>
5347
);
5448
expect(setReferenceNode).toHaveBeenCalled();
@@ -60,14 +54,11 @@ describe('Arrow component', () => {
6054
// HACK: wrapping DIV needed to make Enzyme happy for now
6155
mount(
6256
<div>
63-
<ManagerContext.Provider
64-
value={{
65-
setReferenceNode,
66-
referenceNode: undefined,
67-
}}
57+
<ManagerReferenceNodeSetterContext.Provider
58+
value={setReferenceNode}
6859
>
6960
<Reference>{({ ref }) => <div ref={ref} />}</Reference>
70-
</ManagerContext.Provider>
61+
</ManagerReferenceNodeSetterContext.Provider>
7162
</div>
7263
);
7364
expect(warning).toHaveBeenCalledWith(
@@ -80,13 +71,11 @@ describe('Arrow component', () => {
8071
// HACK: wrapping DIV needed to make Enzyme happy for now
8172
mount(
8273
<div>
83-
<ManagerContext.Provider
84-
value={{
85-
referenceNode: undefined,
86-
}}
74+
<ManagerReferenceNodeSetterContext.Provider
75+
value={undefined}
8776
>
8877
<Reference>{({ ref }) => <div ref={ref} />}</Reference>
89-
</ManagerContext.Provider>
78+
</ManagerReferenceNodeSetterContext.Provider>
9079
</div>
9180
);
9281
expect(warning).toHaveBeenCalledWith(

0 commit comments

Comments
 (0)