Skip to content

Commit 6882816

Browse files
fix(mantine): fix FOUC and handle error styles (#309)
1 parent d7cb3fc commit 6882816

File tree

5 files changed

+139
-17
lines changed

5 files changed

+139
-17
lines changed

mantine/app/clientStyleContext.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createContext } from "react";
2+
3+
export const ClientStyleContext = createContext<{
4+
reset: () => void;
5+
} | null>(null);

mantine/app/entry.client.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
1-
import { ClientProvider } from "@mantine/remix";
1+
import { CacheProvider } from "@emotion/react";
2+
import { createEmotionCache } from "@mantine/core";
23
import { RemixBrowser } from "@remix-run/react";
3-
import { startTransition, StrictMode } from "react";
4+
import { startTransition, StrictMode, useState } from "react";
45
import { hydrateRoot } from "react-dom/client";
56

7+
import { ClientStyleContext } from "./clientStyleContext";
8+
9+
function ClientStyleProvider({ children }: { children: React.ReactNode }) {
10+
const createCache = () => createEmotionCache({ key: "css" });
11+
const [cache, setCache] = useState(createCache());
12+
const reset = () => setCache(createCache());
13+
14+
return (
15+
<ClientStyleContext.Provider value={{ reset }}>
16+
<CacheProvider value={cache}>{children}</CacheProvider>
17+
</ClientStyleContext.Provider>
18+
);
19+
}
20+
621
startTransition(() => {
722
hydrateRoot(
823
document,
924
<StrictMode>
10-
<ClientProvider>
25+
<ClientStyleProvider>
1126
<RemixBrowser />
12-
</ClientProvider>
13-
</StrictMode>,
27+
</ClientStyleProvider>
28+
</StrictMode>
1429
);
1530
});

mantine/app/root.tsx

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import type { ColorScheme } from "@mantine/core";
22
import {
3-
MantineProvider,
3+
Box,
44
ColorSchemeProvider,
55
createEmotionCache,
6+
MantineProvider,
7+
Title,
8+
useEmotionCache,
69
} from "@mantine/core";
10+
import { useIsomorphicEffect } from "@mantine/hooks";
11+
import { StylesPlaceholder } from "@mantine/remix";
712
import type { MetaFunction } from "@remix-run/node";
813
import {
914
Links,
@@ -12,8 +17,11 @@ import {
1217
Outlet,
1318
Scripts,
1419
ScrollRestoration,
20+
useCatch,
1521
} from "@remix-run/react";
16-
import { useState } from "react";
22+
import { useContext, useRef, useState } from "react";
23+
24+
import { ClientStyleContext } from "./clientStyleContext";
1725

1826
export const meta: MetaFunction = () => ({
1927
charset: "utf-8",
@@ -23,18 +31,86 @@ export const meta: MetaFunction = () => ({
2331

2432
createEmotionCache({ key: "mantine" });
2533

34+
export function CatchBoundary() {
35+
const caught = useCatch();
36+
37+
return (
38+
<Document title={`${caught.status} ${caught.statusText}`}>
39+
<Box p="lg">
40+
<Title color="red">
41+
[CatchBoundary]: {caught.status} {caught.statusText}
42+
</Title>
43+
</Box>
44+
</Document>
45+
);
46+
}
47+
48+
export function ErrorBoundary({ error }: { error: Error }) {
49+
return (
50+
<Document title="Error!">
51+
<Box p="lg">
52+
<Title color="red">
53+
[ErrorBoundary]: There was an error: {error.message}
54+
</Title>
55+
</Box>
56+
</Document>
57+
);
58+
}
59+
2660
export default function App() {
61+
return (
62+
<Document>
63+
<Outlet />
64+
</Document>
65+
);
66+
}
67+
68+
function Document({
69+
title,
70+
children,
71+
}: {
72+
title?: string;
73+
children: React.ReactNode;
74+
}) {
75+
const clientStyleData = useContext(ClientStyleContext);
76+
const cache = useEmotionCache();
77+
const reinjectStylesRef = useRef(true);
78+
79+
// Only executed on client
80+
// When a top level ErrorBoundary or CatchBoundary are rendered,
81+
// the document head gets removed, so we have to create the style tags
82+
useIsomorphicEffect(() => {
83+
if (!reinjectStylesRef.current) {
84+
return;
85+
}
86+
87+
// re-link sheet container
88+
cache.sheet.container = document.head;
89+
90+
// re-inject tags
91+
const tags = cache.sheet.tags;
92+
cache.sheet.flush();
93+
tags.forEach((tag) => {
94+
(cache.sheet as any)._insertTag(tag);
95+
});
96+
97+
// reset cache to re-apply global styles
98+
clientStyleData?.reset();
99+
100+
// ensure we only do this once per mount
101+
reinjectStylesRef.current = false;
102+
}, []);
103+
27104
return (
28105
<html lang="en">
29106
<head>
107+
<StylesPlaceholder />
108+
{title ? <title>{title}</title> : null}
30109
<Meta />
31110
<Links />
32111
</head>
33112
<body>
34-
<MantineTheme>
35-
<Outlet />
36-
</MantineTheme>
37-
113+
<MantineTheme>{children}</MantineTheme>
38114
<ScrollRestoration />
39115
<Scripts />
40116
<LiveReload />

mantine/app/routes/error.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function ErrorRoute() {
2+
throw new Error("This route throws on render!");
3+
}

mantine/app/routes/index.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
1-
import { Switch, useMantineColorScheme } from "@mantine/core";
1+
import {
2+
List,
3+
Stack,
4+
Switch,
5+
Text,
6+
Title,
7+
useMantineColorScheme,
8+
} from "@mantine/core";
9+
import { Link } from "@remix-run/react";
210

311
export default function Index() {
412
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
513
const dark = colorScheme === "dark";
614

715
return (
8-
<Switch
9-
color={dark ? "yellow" : "blue"}
10-
label="Dark theme"
11-
onClick={() => toggleColorScheme()}
12-
/>
16+
<Stack spacing="md" p="lg">
17+
<Title>Welcome to Remix with Mantine Example</Title>
18+
<Switch
19+
color={dark ? "yellow" : "blue"}
20+
label="Dark theme"
21+
onClick={() => toggleColorScheme()}
22+
/>
23+
<List>
24+
<List.Item>
25+
<Text>
26+
<Link to="/error">Error page</Link>
27+
</Text>
28+
</List.Item>
29+
<List.Item>
30+
<Text>
31+
<Link to="/404">404 page</Link>
32+
</Text>
33+
</List.Item>
34+
</List>
35+
</Stack>
1336
);
1437
}

0 commit comments

Comments
 (0)