Skip to content

Commit 2c66b4f

Browse files
authored
ref(ui): defer showing the fallback for lazy-loaded files (#102031)
with [route intent preloading](#102574), we show way fewer loaders for lazy-loading assets. However, the loader still shows up when you hard reload a page, or navigate there for the first time. this still leads to cascading loaders, with the first loader (the vertically centered one) being from lazy-loading the static asset: https://github.com/user-attachments/assets/5a85bf86-74ee-4d1d-b2a9-10121bc7160f since lazy-loading static assets is usually fast, by deferring the loader by 300ms, we get a better experience for those cases, while still showing a loader should it really take some time: https://github.com/user-attachments/assets/35945187-d594-43b1-8f79-1b8bef4354c7
1 parent f5bd838 commit 2c66b4f

File tree

2 files changed

+39
-9
lines changed

2 files changed

+39
-9
lines changed

static/app/components/lazyLoad.spec.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {lazy} from 'react';
22

3-
import {render, screen} from 'sentry-test/reactTestingLibrary';
3+
import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
44

55
import LazyLoad from 'sentry/components/lazyLoad';
66

@@ -19,17 +19,22 @@ function BarComponent() {
1919
type ResolvedComponent = {default: React.ComponentType<TestProps>};
2020

2121
describe('LazyLoad', () => {
22+
beforeEach(() => {
23+
jest.useFakeTimers();
24+
});
2225
afterEach(() => {
2326
jest.restoreAllMocks();
27+
jest.useRealTimers();
2428
});
2529

26-
it('renders with a loading indicator when promise is not resolved yet', () => {
30+
it('renders with a loading indicator when promise is not resolved yet', async () => {
2731
const importTest = new Promise<ResolvedComponent>(() => {});
2832
const getComponent = () => importTest;
2933
render(<LazyLoad LazyComponent={lazy(getComponent)} />);
3034

31-
// Should be loading
32-
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
35+
await waitFor(() => {
36+
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
37+
});
3338
});
3439

3540
it('renders when given a promise of a "foo" component', async () => {
@@ -41,7 +46,9 @@ describe('LazyLoad', () => {
4146
render(<LazyLoad LazyComponent={lazy(() => importFoo)} />);
4247

4348
// Should be loading
44-
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
49+
await waitFor(() => {
50+
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
51+
});
4552

4653
// resolve with foo
4754
doResolve!({default: FooComponent});
@@ -72,7 +79,9 @@ describe('LazyLoad', () => {
7279

7380
// First render Foo
7481
const {rerender} = render(<LazyLoad LazyComponent={lazy(() => importFoo)} />);
75-
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
82+
await waitFor(() => {
83+
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
84+
});
7685

7786
// resolve with foo
7887
doResolve!({default: FooComponent});

static/app/components/lazyLoad.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type {ErrorInfo} from 'react';
2-
import {Component, Suspense} from 'react';
1+
import {Component, Suspense, useEffect, useState, type ErrorInfo} from 'react';
32
import * as Sentry from '@sentry/react';
43

54
import {Container, Flex} from 'sentry/components/core/layout';
@@ -23,6 +22,26 @@ type Props<C extends React.LazyExoticComponent<C>> = React.ComponentProps<C> & {
2322
loadingFallback?: React.ReactNode | undefined;
2423
};
2524

25+
function DeferredLoader({
26+
children,
27+
fallback = null,
28+
}: {
29+
children: React.ReactNode;
30+
fallback?: React.ReactNode;
31+
}) {
32+
const [loaded, setLoaded] = useState(false);
33+
34+
useEffect(() => {
35+
const timer = setTimeout(() => {
36+
setLoaded(true);
37+
}, 300);
38+
39+
return () => clearTimeout(timer);
40+
}, []);
41+
42+
return loaded ? children : fallback;
43+
}
44+
2645
/**
2746
* LazyLoad is used to dynamically load codesplit components via a `import`
2847
* call. This is primarily used in our routing tree.
@@ -43,7 +62,9 @@ function LazyLoad<C extends React.LazyExoticComponent<any>>({
4362
fallback={
4463
loadingFallback ?? (
4564
<Flex flex="1" align="center" column="1 / -1">
46-
<LoadingIndicator />
65+
<DeferredLoader fallback={<LoadingIndicator style={{display: 'none'}} />}>
66+
<LoadingIndicator />
67+
</DeferredLoader>
4768
</Flex>
4869
)
4970
}

0 commit comments

Comments
 (0)