Fix a crash in Suspense with findDOMNode
Dan Abramov committed
Apr 3, 2019 at 13:21 UTC
41aa345d2bde261c2fb4a4ef89e379640c88be67
2 files changed
+67
-2
packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js
+44
@@ -233,4 +233,48 @@ describe('ReactDOMSuspensePlaceholder', () => {
233
await Lazy;
234
expect(log).toEqual(['cDU first', 'cDU second']);
235
});
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
+ });
280
});
packages/react-reconciler/src/ReactFiberTreeReflection.js
+23
-2
@@ -21,6 +21,8 @@ import {
21
HostRoot,
22
HostPortal,
23
HostText,
24
+ Fragment,
25
+ SuspenseComponent,
26
} from 'shared/ReactWorkTags';
27
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
28
@@ -119,8 +121,27 @@ export function findCurrentFiberUsingSlowPath(fiber: Fiber): Fiber | null {
121
let parentA = a.return;
122
let parentB = parentA ? parentA.alternate : null;
123
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
+ }
145
}
146
147
// If both copies of the parent fiber point to the same child, we can