Skip to content

Commit 70fa619

Browse files
authored
Avoid lazy components without result going in throw loop (#4939)
1 parent 078ed0e commit 70fa619

File tree

3 files changed

+119
-55
lines changed

3 files changed

+119
-55
lines changed

compat/src/suspense.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,18 +208,23 @@ Suspense.prototype.render = function (props, state) {
208208

209209
export function lazy(loader) {
210210
let prom;
211-
let component;
211+
let component = null;
212212
let error;
213+
let resolved;
213214

214215
function Lazy(props) {
215216
if (!prom) {
216217
prom = loader();
217218
prom.then(
218219
exports => {
219-
component = exports.default || exports;
220+
if (exports) {
221+
component = exports.default || exports;
222+
}
223+
resolved = true;
220224
},
221225
e => {
222226
error = e;
227+
resolved = true;
223228
}
224229
);
225230
}
@@ -228,11 +233,11 @@ export function lazy(loader) {
228233
throw error;
229234
}
230235

231-
if (!component) {
236+
if (!resolved) {
232237
throw prom;
233238
}
234239

235-
return createElement(component, props);
240+
return component ? createElement(component, props) : null;
236241
}
237242

238243
Lazy.displayName = 'Lazy';

compat/test/browser/suspense.test.jsx

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ class Catcher extends Component {
3434
}
3535

3636
render(props, state) {
37-
return state.error
38-
? <div>Catcher did catch: {state.error.message}</div>
39-
: props.children;
37+
return state.error ? (
38+
<div>Catcher did catch: {state.error.message}</div>
39+
) : (
40+
props.children
41+
);
4042
}
4143
}
4244

@@ -104,6 +106,59 @@ describe('suspense', () => {
104106
});
105107
});
106108

109+
it('should handle lazy component that rejects without returning a component', async () => {
110+
const errorSpy = vi.fn();
111+
let renderCount = 0;
112+
113+
let resolve;
114+
function fakeImport() {
115+
const p = new Promise((_, reject) => {
116+
resolve = () => {
117+
reject(new Error('import failed'));
118+
return p;
119+
};
120+
});
121+
return p;
122+
}
123+
124+
const SomeComponent = lazy(() =>
125+
fakeImport().catch(e => {
126+
console.log('caught', e);
127+
errorSpy(e);
128+
})
129+
);
130+
131+
const App = () => {
132+
renderCount++;
133+
if (renderCount > 5) {
134+
throw new Error('Infinite loop detected!');
135+
}
136+
137+
console.log('RENDER COUNT', renderCount);
138+
return (
139+
<div>
140+
<Suspense fallback={<div>loading</div>}>
141+
<SomeComponent />
142+
</Suspense>
143+
</div>
144+
);
145+
};
146+
147+
render(<App />, scratch);
148+
rerender();
149+
150+
expect(scratch.innerHTML).to.contain('loading');
151+
152+
const assert = () => {
153+
rerender();
154+
155+
expect(scratch.innerHTML).to.contain('<div></div>');
156+
expect(errorSpy).toHaveBeenCalledOnce;
157+
};
158+
159+
resolve().then(assert).catch(assert);
160+
});
161+
107162
it('should reset hooks of components', () => {
108163
/** @type {(v) => void} */
109164
let set;
@@ -190,11 +245,13 @@ describe('suspense', () => {
190245
};
191246
}, []);
192247

193-
return state
194-
? <div>{children}</div>
195-
: <div>
196-
<p>hi</p>
197-
</div>;
248+
return state ? (
249+
<div>{children}</div>
250+
) : (
251+
<div>
252+
<p>hi</p>
253+
</div>
254+
);
198255
};
199256

200257
render(

test/browser/refs.test.jsx

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createElement, render, Component, createRef, Fragment } from 'preact';
33
import { setupScratch, teardown } from '../_util/helpers';
44
import { vi } from 'vitest';
55

6-
// gives call count and argument errors names (otherwise sinon just uses "spy"):
6+
// gives call count and argument errors names (otherwise vitest just uses "spy"):
77
let spy = (name, ...args) => {
88
let spy = vi.fn(...args);
99
spy.displayName = `spy('${name}')`;
@@ -241,13 +241,15 @@ describe('refs', () => {
241241
it('should correctly set nested child refs', () => {
242242
const ref = createRef();
243243
const App = ({ open }) =>
244-
open
245-
? <div class="open" key="open">
246-
<div ref={ref} />
247-
</div>
248-
: <div class="closes" key="closed">
249-
<div ref={ref} />
250-
</div>;
244+
open ? (
245+
<div class="open" key="open">
246+
<div ref={ref} />
247+
</div>
248+
) : (
249+
<div class="closes" key="closed">
250+
<div ref={ref} />
251+
</div>
252+
);
251253

252254
render(<App />, scratch);
253255
expect(ref.current).to.not.be.null;
@@ -360,8 +362,39 @@ describe('refs', () => {
360362
render(props, { phase }) {
361363
return (
362364
<Fragment>
363-
{phase === 1
364-
? <div>
365+
{phase === 1 ? (
366+
<div>
367+
<div
368+
ref={r =>
369+
r
370+
? calls.push('adding ref to two')
371+
: calls.push('removing ref from two')
372+
}
373+
>
374+
Element two
375+
</div>
376+
<div
377+
ref={r =>
378+
r
379+
? calls.push('adding ref to three')
380+
: calls.push('removing ref from three')
381+
}
382+
>
383+
Element three
384+
</div>
385+
</div>
386+
) : phase === 2 ? (
387+
<div class="outer">
388+
<div
389+
ref={r =>
390+
r
391+
? calls.push('adding ref to one')
392+
: calls.push('removing ref from one')
393+
}
394+
>
395+
Element one
396+
</div>
397+
<div class="wrapper">
365398
<div
366399
ref={r =>
367400
r
@@ -381,39 +414,8 @@ describe('refs', () => {
381414
Element three
382415
</div>
383416
</div>
384-
: phase === 2
385-
? <div class="outer">
386-
<div
387-
ref={r =>
388-
r
389-
? calls.push('adding ref to one')
390-
: calls.push('removing ref from one')
391-
}
392-
>
393-
Element one
394-
</div>
395-
<div class="wrapper">
396-
<div
397-
ref={r =>
398-
r
399-
? calls.push('adding ref to two')
400-
: calls.push('removing ref from two')
401-
}
402-
>
403-
Element two
404-
</div>
405-
<div
406-
ref={r =>
407-
r
408-
? calls.push('adding ref to three')
409-
: calls.push('removing ref from three')
410-
}
411-
>
412-
Element three
413-
</div>
414-
</div>
415-
</div>
416-
: null}
417+
</div>
418+
) : null}
417419
</Fragment>
418420
);
419421
}

0 commit comments

Comments
 (0)