Skip to content

Commit 9cf03f8

Browse files
authored
refactor: moving the createRouter logic out of the entry file (#413)
* refactor: move the as much of the logic into the router's Wrap and InnerWrap rendering options * refactor: move `FullPageLoadingSpinner` into its own independent component * refactor: use a named fragment * refactor: use the filename `main.css` instead of `index.css` * refactor: use the filename `entry-app.tsx` instead of `app-entry.tsx` * chore: update import
1 parent 033aa8b commit 9cf03f8

11 files changed

+189
-165
lines changed

components.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"tsx": true,
66
"tailwind": {
77
"config": "tailwind.config.cjs",
8-
"css": "src/index.css",
8+
"css": "src/main.css",
99
"baseColor": "slate",
1010
"cssVariables": true
1111
},

src/app-entry.tsx

-159
This file was deleted.

src/components/cache-buster.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import * as React from "react";
55
import { compare } from "compare-versions";
66

7+
import { useEventListener } from "@/lib/hooks/useEventListener";
8+
79
type OnCacheClearFn = (callback?: () => void) => Promise<void>;
810

911
const CacheBusterContext = React.createContext<{
@@ -165,4 +167,20 @@ function useCacheBuster() {
165167
return context;
166168
}
167169

168-
export { CacheBuster, useCacheBuster };
170+
function CacheDocumentFocusChecker() {
171+
const documentRef = React.useRef<Document>(document);
172+
173+
const { checkCacheStatus } = useCacheBuster();
174+
175+
const onVisibilityChange = () => {
176+
if (document.visibilityState === "visible") {
177+
checkCacheStatus();
178+
}
179+
};
180+
181+
useEventListener("visibilitychange", onVisibilityChange, documentRef);
182+
183+
return null;
184+
}
185+
186+
export { CacheBuster, CacheDocumentFocusChecker, useCacheBuster };

src/entry-app.tsx

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import * as React from "react";
2+
import { RouterProvider } from "@tanstack/react-router";
3+
import { useTranslation } from "react-i18next";
4+
import { AuthProvider, useAuth } from "react-oidc-context";
5+
import { Toaster } from "sonner";
6+
7+
import { CacheBuster } from "@/components/cache-buster";
8+
9+
import { useTernaryDarkMode } from "@/lib/hooks/useTernaryDarkMode";
10+
11+
import { APP_VERSION, IS_DEV } from "@/lib/utils/constants";
12+
13+
import "@/lib/config/i18next";
14+
15+
import { FullPageLoadingSpinner } from "@/routes/-components/full-screen-loading-spinner";
16+
17+
import { userManager } from "@/lib/config/oidc-client-ts";
18+
import { createRouter } from "@/lib/config/tanstack-router";
19+
20+
const router = createRouter();
21+
22+
function App() {
23+
const auth = useAuth();
24+
25+
React.useEffect(() => {
26+
if (typeof auth.user === "undefined") return;
27+
28+
router.invalidate();
29+
}, [auth.user]);
30+
31+
return typeof auth.user === "undefined" ? (
32+
<FullPageLoadingSpinner />
33+
) : (
34+
<RouterProvider router={router} context={{ auth }} />
35+
);
36+
}
37+
38+
export default function UserApp() {
39+
const { i18n } = useTranslation();
40+
const theme = useTernaryDarkMode();
41+
42+
const dir = i18n.dir();
43+
44+
return (
45+
<React.Suspense fallback={<FullPageLoadingSpinner />}>
46+
<CacheBuster
47+
loadingComponent={<FullPageLoadingSpinner />}
48+
currentVersion={APP_VERSION}
49+
isVerboseMode={IS_DEV}
50+
isEnabled={IS_DEV === false}
51+
reloadOnDowngrade
52+
>
53+
<AuthProvider userManager={userManager}>
54+
<App />
55+
</AuthProvider>
56+
<Toaster
57+
theme={theme.ternaryDarkMode}
58+
dir={dir}
59+
position="bottom-center"
60+
className="toaster group"
61+
toastOptions={{
62+
classNames: {
63+
toast:
64+
"group toast group-[.toaster]:bg-[var(--toast-background)] group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
65+
description: "group-[.toast]:text-muted-foreground",
66+
actionButton:
67+
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
68+
cancelButton:
69+
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
70+
},
71+
}}
72+
closeButton
73+
/>
74+
</CacheBuster>
75+
</React.Suspense>
76+
);
77+
}

src/lib/config/tanstack-query.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { broadcastQueryClient } from "@tanstack/query-broadcast-client-experimental";
12
import { QueryClient } from "@tanstack/react-query";
23

4+
import { APP_VERSION } from "@/lib/utils/constants";
5+
36
// Create a client for react-query
47
export const queryClient = new QueryClient({
58
defaultOptions: {
@@ -8,3 +11,8 @@ export const queryClient = new QueryClient({
811
},
912
},
1013
});
14+
15+
broadcastQueryClient({
16+
queryClient,
17+
broadcastChannel: APP_VERSION,
18+
});

src/lib/config/tanstack-router.tsx

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from "react";
2+
import { QueryClientProvider } from "@tanstack/react-query";
3+
import {
4+
createRouter as createTanStackRouter,
5+
parseSearchWith,
6+
stringifySearchWith,
7+
} from "@tanstack/react-router";
8+
import * as JSURL2 from "jsurl2";
9+
10+
import { CacheDocumentFocusChecker } from "@/components/cache-buster";
11+
import { icons } from "@/components/ui/icons";
12+
import { TooltipProvider } from "@/components/ui/tooltip";
13+
14+
import { GlobalDialogProvider } from "@/lib/context/modals";
15+
16+
import { queryClient } from "@/lib/config/tanstack-query";
17+
18+
import { routeTree } from "@/route-tree.gen";
19+
20+
export function createRouter() {
21+
const router = createTanStackRouter({
22+
routeTree,
23+
defaultPreload: "intent",
24+
defaultPreloadStaleTime: 0,
25+
defaultViewTransition: true,
26+
defaultPendingComponent: function RouterPendingComponent() {
27+
<div className="grid min-h-full w-full place-items-center">
28+
<icons.Loading className="h-24 w-24 animate-spin text-foreground" />
29+
</div>;
30+
},
31+
parseSearch: parseSearchWith((value) => JSURL2.parse(value)),
32+
stringifySearch: stringifySearchWith(
33+
(value) => JSURL2.stringify(value),
34+
(value) => JSURL2.parse(value)
35+
),
36+
context: {
37+
queryClient,
38+
auth: undefined!, // will be set by an AuthWrapper
39+
},
40+
trailingSlash: "never",
41+
Wrap: function ({ children }) {
42+
return (
43+
<QueryClientProvider client={queryClient}>
44+
<GlobalDialogProvider>
45+
<TooltipProvider>{children}</TooltipProvider>
46+
</GlobalDialogProvider>
47+
</QueryClientProvider>
48+
);
49+
},
50+
InnerWrap: function ({ children }) {
51+
return (
52+
<React.Fragment>
53+
<CacheDocumentFocusChecker />
54+
{children}
55+
</React.Fragment>
56+
);
57+
},
58+
});
59+
60+
return router;
61+
}
62+
63+
declare module "@tanstack/react-router" {
64+
interface Register {
65+
router: ReturnType<typeof createRouter>;
66+
}
67+
}

src/index.css src/main.css

File renamed without changes.

src/main.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React from "react";
22
import ReactDOM from "react-dom/client";
33

4-
import App from "./app-entry";
4+
import UserApp from "./entry-app";
55

6-
import "./index.css";
6+
import "./main.css";
77

88
const documentElement = document.getElementById("root");
99

@@ -13,6 +13,6 @@ if (!documentElement) {
1313

1414
ReactDOM.createRoot(documentElement).render(
1515
<React.StrictMode>
16-
<App />
16+
<UserApp />
1717
</React.StrictMode>
1818
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { icons } from "@/components/ui/icons";
2+
3+
export function FullPageLoadingSpinner() {
4+
return (
5+
<div className="grid min-h-full w-full place-items-center">
6+
<icons.Loading className="h-24 w-24 animate-spin text-foreground" />
7+
</div>
8+
);
9+
}

0 commit comments

Comments
 (0)