Skip to content

Commit 6021430

Browse files
committed
Render lazy-loaded components synchronously if already loaded
Make lazy-loading components created by `lazy` render synchronously (ie. render the actual component on the first render) if the component was already loaded by a previous instance of the lazy wrapper component. In h the async rendering was causing annotation cards to transition their height slightly after appearing. This change doesn't prevent the animation from happening when annotation cards first appear, but does prevent it on subsequen renders.
1 parent a009bd1 commit 6021430

File tree

2 files changed

+32
-5
lines changed

2 files changed

+32
-5
lines changed

src/util/lazy.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,14 @@ export function lazy<P>(
4343
load: () => Promise<ComponentModule<P> | FunctionalComponent<P>>,
4444
{ errorFallback, fallback }: LazyOptions<P>,
4545
) {
46+
// Cache resolved component so that instances created after it has loaded
47+
// will render synchronously.
48+
let resolved: FunctionalComponent<P> | undefined;
49+
4650
function Lazy(props: P & JSX.IntrinsicAttributes) {
47-
const [component, setComponent] = useState<FunctionalComponent<P>>();
51+
const [component, setComponent] = useState<
52+
FunctionalComponent<P> | undefined
53+
>(() => resolved);
4854
const [error, setError] = useState<Error | null>(null);
4955
const [loading, setLoading] = useState(false);
5056

@@ -63,12 +69,18 @@ export function lazy<P>(
6369
if (!component && !loading) {
6470
setLoading(true);
6571
load()
66-
.then(component => {
67-
if (isComponentModule(component)) {
68-
setComponent(() => component.default);
72+
.then(componentOrModule => {
73+
let component;
74+
if (isComponentModule(componentOrModule)) {
75+
component = componentOrModule.default;
6976
} else {
70-
setComponent(() => component);
77+
component = componentOrModule as FunctionalComponent<P>;
7178
}
79+
80+
// Cache so we can render synchronously in future.
81+
resolved = component;
82+
83+
setComponent(() => component);
7284
})
7385
.catch(setError)
7486
.finally(() => {

src/util/test/lazy-test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ describe('lazy', () => {
5555
);
5656
});
5757

58+
it('renders component on initial render if already loaded', async () => {
59+
// First render, which triggers lazy loading.
60+
fakeLoader.returns(Promise.resolve(fakeComponent));
61+
const wrapper = mount(<LazyComponent text="test" />);
62+
await delay(0);
63+
wrapper.update();
64+
assert.isTrue(wrapper.exists('[data-testid="loaded-component"]'));
65+
wrapper.unmount();
66+
67+
// Second render, which should synchronously render the already-loaded
68+
// component.
69+
const wrapper2 = mount(<LazyComponent text="test" />);
70+
assert.isTrue(wrapper2.exists('[data-testid="loaded-component"]'));
71+
});
72+
5873
it('supports load callback returning a module', async () => {
5974
fakeLoader.returns(Promise.resolve({ default: fakeComponent }));
6075
const wrapper = mount(<LazyComponent text="test" />);

0 commit comments

Comments
 (0)