Skip to content

Commit b54588a

Browse files
frankwallisjquense
authored andcommitted
Alternative fix for #55 (#72)
* Alternative fix for #55 * don't hold onto refs, bind them to callback functions
1 parent e694fa5 commit b54588a

File tree

2 files changed

+103
-32
lines changed

2 files changed

+103
-32
lines changed

src/TransitionGroup.js

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class TransitionGroup extends React.Component {
4141
let initialChildMapping = this.state.children;
4242
for (let key in initialChildMapping) {
4343
if (initialChildMapping[key]) {
44-
this.performAppear(key);
44+
this.performAppear(key, this.childRefs[key]);
4545
}
4646
}
4747
}
@@ -60,15 +60,15 @@ class TransitionGroup extends React.Component {
6060
for (let key in nextChildMapping) {
6161
let hasPrev = prevChildMapping && prevChildMapping.hasOwnProperty(key);
6262
if (nextChildMapping[key] && !hasPrev &&
63-
!this.currentlyTransitioningKeys[key]) {
63+
!this.currentlyTransitioningKeys[key]) {
6464
this.keysToEnter.push(key);
6565
}
6666
}
6767

6868
for (let key in prevChildMapping) {
6969
let hasNext = nextChildMapping && nextChildMapping.hasOwnProperty(key);
7070
if (prevChildMapping[key] && !hasNext &&
71-
!this.currentlyTransitioningKeys[key]) {
71+
!this.currentlyTransitioningKeys[key]) {
7272
this.keysToLeave.push(key);
7373
}
7474
}
@@ -79,30 +79,27 @@ class TransitionGroup extends React.Component {
7979
componentDidUpdate() {
8080
let keysToEnter = this.keysToEnter;
8181
this.keysToEnter = [];
82-
keysToEnter.forEach(this.performEnter);
82+
keysToEnter.forEach(key => this.performEnter(key, this.childRefs[key]));
8383

8484
let keysToLeave = this.keysToLeave;
8585
this.keysToLeave = [];
86-
keysToLeave.forEach(this.performLeave);
86+
keysToLeave.forEach(key => this.performLeave(key, this.childRefs[key]));
8787
}
8888

89-
performAppear = (key) => {
89+
performAppear = (key, component) => {
9090
this.currentlyTransitioningKeys[key] = true;
9191

92-
let component = this.childRefs[key];
93-
9492
if (component.componentWillAppear) {
9593
component.componentWillAppear(
96-
this._handleDoneAppearing.bind(this, key),
94+
this._handleDoneAppearing.bind(this, key, component),
9795
);
9896
} else {
99-
this._handleDoneAppearing(key);
97+
this._handleDoneAppearing(key, component);
10098
}
10199
};
102100

103-
_handleDoneAppearing = (key) => {
104-
let component = this.childRefs[key];
105-
if (component && component.componentDidAppear) {
101+
_handleDoneAppearing = (key, component) => {
102+
if (component.componentDidAppear) {
106103
component.componentDidAppear();
107104
}
108105

@@ -112,27 +109,24 @@ class TransitionGroup extends React.Component {
112109

113110
if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) {
114111
// This was removed before it had fully appeared. Remove it.
115-
this.performLeave(key);
112+
this.performLeave(key, component);
116113
}
117114
};
118115

119-
performEnter = (key) => {
116+
performEnter = (key, component) => {
120117
this.currentlyTransitioningKeys[key] = true;
121118

122-
let component = this.childRefs[key];
123-
124119
if (component.componentWillEnter) {
125120
component.componentWillEnter(
126-
this._handleDoneEntering.bind(this, key),
121+
this._handleDoneEntering.bind(this, key, component),
127122
);
128123
} else {
129-
this._handleDoneEntering(key);
124+
this._handleDoneEntering(key, component);
130125
}
131126
};
132127

133-
_handleDoneEntering = (key) => {
134-
let component = this.childRefs[key];
135-
if (component && component.componentDidEnter) {
128+
_handleDoneEntering = (key, component) => {
129+
if (component.componentDidEnter) {
136130
component.componentDidEnter();
137131
}
138132

@@ -142,28 +136,25 @@ class TransitionGroup extends React.Component {
142136

143137
if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) {
144138
// This was removed before it had fully entered. Remove it.
145-
this.performLeave(key);
139+
this.performLeave(key, component);
146140
}
147141
};
148142

149-
performLeave = (key) => {
143+
performLeave = (key, component) => {
150144
this.currentlyTransitioningKeys[key] = true;
151145

152-
let component = this.childRefs[key];
153146
if (component.componentWillLeave) {
154-
component.componentWillLeave(this._handleDoneLeaving.bind(this, key));
147+
component.componentWillLeave(this._handleDoneLeaving.bind(this, key, component));
155148
} else {
156149
// Note that this is somewhat dangerous b/c it calls setState()
157150
// again, effectively mutating the component before all the work
158151
// is done.
159-
this._handleDoneLeaving(key);
152+
this._handleDoneLeaving(key, component);
160153
}
161154
};
162155

163-
_handleDoneLeaving = (key) => {
164-
let component = this.childRefs[key];
165-
166-
if (component && component.componentDidLeave) {
156+
_handleDoneLeaving = (key, component) => {
157+
if (component.componentDidLeave) {
167158
component.componentDidLeave();
168159
}
169160

@@ -173,7 +164,7 @@ class TransitionGroup extends React.Component {
173164

174165
if (currentChildMapping && currentChildMapping.hasOwnProperty(key)) {
175166
// This entered again before it fully left. Add it again.
176-
this.performEnter(key);
167+
this.keysToEnter.push(key);
177168
} else {
178169
this.setState((state) => {
179170
let newChildren = Object.assign({}, state.children);

test/TransitionGroup-test.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,4 +440,84 @@ describe('TransitionGroup', () => {
440440
// ' in Component (at **)'
441441
// );
442442
});
443+
444+
it('should not throw when enter callback is called and is now leaving', () => {
445+
class Child extends React.Component {
446+
componentWillReceiveProps() {
447+
if (this.callback) {
448+
this.callback();
449+
}
450+
}
451+
452+
componentWillEnter(callback) {
453+
this.callback = callback;
454+
}
455+
456+
render() {
457+
return (<span />);
458+
}
459+
}
460+
461+
class Component extends React.Component {
462+
render() {
463+
return (
464+
<TransitionGroup>
465+
{this.props.children}
466+
</TransitionGroup>
467+
);
468+
}
469+
}
470+
471+
// render the base component
472+
ReactDOM.render(<Component />, container);
473+
// now make the child enter
474+
ReactDOM.render(
475+
<Component><Child key="child" /></Component>,
476+
container,
477+
);
478+
// rendering the child leaving will call 'componentWillProps' which will trigger the
479+
// callback. This would throw an error previously.
480+
expect(ReactDOM.render.bind(this, <Component />, container)).not.toThrow();
481+
})
482+
483+
it('should not throw when leave callback is called and is now entering', () => {
484+
class Child extends React.Component {
485+
componentWillReceiveProps() {
486+
if (this.callback) {
487+
this.callback();
488+
}
489+
}
490+
491+
componentWillLeave(callback) {
492+
this.callback = callback;
493+
}
494+
495+
render() {
496+
return (<span />);
497+
}
498+
}
499+
500+
class Component extends React.Component {
501+
render() {
502+
return (
503+
<TransitionGroup>
504+
{this.props.children}
505+
</TransitionGroup>
506+
);
507+
}
508+
}
509+
510+
// render the base component
511+
ReactDOM.render(<Component />, container);
512+
// now make the child enter
513+
ReactDOM.render(
514+
<Component><Child key="child" /></Component>,
515+
container,
516+
);
517+
// make the child leave
518+
ReactDOM.render(<Component />, container);
519+
// rendering the child entering again will call 'componentWillProps' which will trigger the
520+
// callback. This would throw an error previously.
521+
expect(ReactDOM.render.bind(this, <Component><Child key="child" /></Component>, container)).not.toThrow();
522+
})
443523
});

0 commit comments

Comments
 (0)