Skip to content

Commit eded1a8

Browse files
committed
frontend: show global loading page until OIerDbClient is available for use
1 parent 878cbe1 commit eded1a8

4 files changed

Lines changed: 73 additions & 3 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useEffect, useState } from 'react';
2+
3+
/**
4+
* Returns true only after the component has hydrated on the client.
5+
* Use this to keep initial client render identical to SSR markup,
6+
* then switch to live, client-only state after hydration.
7+
*/
8+
export function useHydrated() {
9+
const [hydrated, setHydrated] = useState(false);
10+
useEffect(() => {
11+
setHydrated(true);
12+
}, []);
13+
return hydrated;
14+
}

apps/frontend/app/libs/client/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ declare global {
99
}
1010

1111
export { getClient, initClient } from './client';
12-
export { getStatus, subscribeToStatusChange } from './status';
12+
export { getStatus, subscribeToStatusChange, waitUntilClientReady } from './status';

apps/frontend/app/libs/client/status.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,19 @@ export const setStatus = (newStatus: OIerDbClientStatusType) => {
3535
globalThis.OIerDbClientStatus = newStatus;
3636
notifyStatusChange(newStatus);
3737
};
38+
39+
export const waitUntilClientReady = () =>
40+
new Promise<void>((resolve) => {
41+
const current = globalThis.OIerDbClientStatus;
42+
if (current && current.type > OIerDbClientStatusEnum.Initializing) {
43+
resolve();
44+
return;
45+
}
46+
47+
const unsubscribe = subscribeToStatusChange((s) => {
48+
if (s.type >= OIerDbClientStatusEnum.InitializedPartially) {
49+
unsubscribe();
50+
resolve();
51+
}
52+
});
53+
});

apps/frontend/app/root.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import '@mantine/core/styles.css';
2-
31
import { ColorSchemeScript, mantineHtmlProps, MantineProvider } from '@mantine/core';
2+
import { useMemo } from 'react';
43
import {
54
isRouteErrorResponse,
65
Links,
@@ -10,9 +9,15 @@ import {
109
ScrollRestoration,
1110
} from 'react-router';
1211

12+
import { useClientStatus } from '~/hooks/use-client';
13+
import { useHydrated } from '~/hooks/use-hydrated';
14+
import { waitUntilClientReady } from '~/libs/client';
15+
1316
import type { Route } from './+types/root';
1417
import { theme } from './theme';
1518

19+
import '@mantine/core/styles.css';
20+
1621
export function Layout({ children }: { children: React.ReactNode }) {
1722
return (
1823
<html lang="zh-CN" {...mantineHtmlProps}>
@@ -40,6 +45,41 @@ export default function App() {
4045
return <Outlet />;
4146
}
4247

48+
// Block all client-side data fetching until the client is initialized.
49+
export const clientMiddleware: Route.ClientMiddlewareFunction[] = [
50+
async () => {
51+
console.log('Waiting for OIerDbClient to be ready in client middleware...');
52+
await waitUntilClientReady();
53+
console.log('OIerDbClient is ready, continuing client middleware...');
54+
},
55+
];
56+
57+
// Global loading UI while hydrating or while the root is waiting for client initialization
58+
export function HydrateFallback(_props: Route.HydrateFallbackProps) {
59+
// Render SSR-stable text on the first client render; switch to live status after hydration
60+
const hydrated = useHydrated();
61+
const status = useClientStatus();
62+
const text = useMemo(() => (hydrated ? status?.text || '请稍候' : '请稍候'), [hydrated, status]);
63+
64+
return (
65+
<div
66+
style={{
67+
minHeight: '100dvh',
68+
display: 'flex',
69+
alignItems: 'center',
70+
justifyContent: 'center',
71+
padding: 16,
72+
fontSize: 16,
73+
}}
74+
>
75+
<div>
76+
<div style={{ fontWeight: 600, marginBottom: 8 }}>正在准备应用…</div>
77+
<div style={{ opacity: 0.75 }}>{text}</div>
78+
</div>
79+
</div>
80+
);
81+
}
82+
4383
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
4484
let message = 'Oops!';
4585
let details = 'An unexpected error occurred.';

0 commit comments

Comments
 (0)