Skip to content

Commit 41aa345

Browse files
gaearonacdlite
authored andcommitted
Fix a crash in Suspense with findDOMNode
1 parent 6d0effa commit 41aa345

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js

+44
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,48 @@ describe('ReactDOMSuspensePlaceholder', () => {
233233
await Lazy;
234234
expect(log).toEqual(['cDU first', 'cDU second']);
235235
});
236+
237+
// Regression test for https://github.com/facebook/react/issues/14188
238+
it('can call findDOMNode() in a suspended component commit phase (#2)', () => {
239+
let suspendOnce = Promise.resolve();
240+
function Suspend() {
241+
if (suspendOnce) {
242+
let promise = suspendOnce;
243+
suspendOnce = null;
244+
throw promise;
245+
}
246+
return null;
247+
}
248+
249+
const log = [];
250+
class Child extends React.Component {
251+
componentDidMount() {
252+
log.push('cDM');
253+
ReactDOM.findDOMNode(this);
254+
}
255+
256+
componentDidUpdate() {
257+
log.push('cDU');
258+
ReactDOM.findDOMNode(this);
259+
}
260+
261+
render() {
262+
return null;
263+
}
264+
}
265+
266+
function App() {
267+
return (
268+
<Suspense fallback="Loading">
269+
<Suspend />
270+
<Child />
271+
</Suspense>
272+
);
273+
}
274+
275+
ReactDOM.render(<App />, container);
276+
expect(log).toEqual(['cDM']);
277+
ReactDOM.render(<App />, container);
278+
expect(log).toEqual(['cDM', 'cDU']);
279+
});
236280
});

packages/react-reconciler/src/ReactFiberTreeReflection.js

+23-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
HostRoot,
2222
HostPortal,
2323
HostText,
24+
Fragment,
25+
SuspenseComponent,
2426
} from 'shared/ReactWorkTags';
2527
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
2628

@@ -119,8 +121,27 @@ export function findCurrentFiberUsingSlowPath(fiber: Fiber): Fiber | null {
119121
let parentA = a.return;
120122
let parentB = parentA ? parentA.alternate : null;
121123
if (!parentA || !parentB) {
122-
// We're at the root.
123-
break;
124+
// We're either at the root, or we're in a special Fragment
125+
// with no alternate, which is how Suspense (un)hiding works.
126+
let maybeSuspenseFragment = parentA || parentB;
127+
if (maybeSuspenseFragment && maybeSuspenseFragment.tag === Fragment) {
128+
const maybeSuspense = maybeSuspenseFragment.return;
129+
if (
130+
maybeSuspense &&
131+
maybeSuspense.tag === SuspenseComponent &&
132+
// If state isn't null, it timed out and we have two Fragment children.
133+
maybeSuspense.memoizedState !== null
134+
) {
135+
parentA = maybeSuspense;
136+
parentB = maybeSuspense;
137+
a = maybeSuspenseFragment;
138+
b = maybeSuspenseFragment;
139+
} else {
140+
break;
141+
}
142+
} else {
143+
break;
144+
}
124145
}
125146

126147
// If both copies of the parent fiber point to the same child, we can

0 commit comments

Comments
 (0)