Skip to content

Commit b7a049f

Browse files
committed
move middleware to root and refactor
1 parent 56a794c commit b7a049f

File tree

9 files changed

+129
-109
lines changed

9 files changed

+129
-109
lines changed

Diff for: app/api/sidebar.ts

-16
This file was deleted.

Diff for: app/api/update-menu-collapse.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { setMenuCollapseState } from "~/modules/menu-collapse.server";
2+
import type { Route } from "./+types/update-menu-collapse";
3+
import type { Info as RootInfo } from "../+types/root";
4+
import { useRouteLoaderData, useSubmit } from "react-router";
5+
import invariant from "tiny-invariant";
6+
import { useCallback } from "react";
7+
8+
export async function action({ request }: Route.ActionArgs) {
9+
let formData = await request.formData();
10+
11+
let category = formData.get("category");
12+
if (!category || typeof category !== "string") {
13+
return new Response("Name is required", { status: 400 });
14+
}
15+
16+
let open = formData.get("open");
17+
if (open !== "true" && open !== "false") {
18+
return new Response("Open must be true or false", { status: 400 });
19+
}
20+
21+
let parsedOpen = open === "true";
22+
23+
setMenuCollapseState(category, parsedOpen);
24+
return parsedOpen;
25+
}
26+
27+
export function useMenuCollapse(category?: string) {
28+
const submit = useSubmit();
29+
const rootLoaderData = useRouteLoaderData<RootInfo["loaderData"]>("root");
30+
31+
invariant(rootLoaderData, "No root loader data found");
32+
33+
const isCollapsed = category
34+
? rootLoaderData.menuCollapseState[category] ?? true
35+
: true;
36+
37+
const submitMenuCollapse = useCallback(
38+
(open: boolean) => {
39+
if (!category) return;
40+
submit(
41+
{ category, open: String(open) },
42+
{
43+
navigate: false,
44+
method: "post",
45+
action: "/_update-menu-collapse",
46+
}
47+
);
48+
},
49+
[category, submit]
50+
);
51+
52+
return [isCollapsed, submitMenuCollapse] as const;
53+
}

Diff for: app/components/docs-menu/menu.tsx

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from "react";
2-
import { Link, useFetcher, useSubmit } from "react-router";
2+
import { Link } from "react-router";
33
import classNames from "classnames";
44

55
import iconsHref from "~/icons.svg";
@@ -8,7 +8,7 @@ import type { MenuDoc } from "~/modules/gh-docs/.server/docs";
88
import { useNavigation } from "~/hooks/use-navigation";
99
import { useDelayedValue } from "~/hooks/use-delayed-value";
1010
import { useHeaderData } from "../docs-header/use-header-data";
11-
import { useSidebarState } from "~/pages/docs-layout";
11+
import { useMenuCollapse } from "~/api/update-menu-collapse";
1212

1313
export function Menu({ menu }: { menu?: MenuDoc[] }) {
1414
// github might be down but the menu but the doc could be cached in memory, so
@@ -91,14 +91,12 @@ function MenuCategoryDetails({
9191
slug,
9292
children,
9393
}: MenuCategoryDetailsType) {
94-
const submit = useSubmit();
95-
const sidebarState = useSidebarState(slug!);
96-
97-
console.log({ slug, sidebarState });
94+
const [isMenuCollapsed, submitMenuCollapse] = useMenuCollapse(slug!);
9895

9996
let { isActive } = useNavigation(slug);
97+
10098
// By default only the active path is open
101-
const [isOpen, setIsOpen] = React.useState(sidebarState);
99+
const [isOpen, setIsOpen] = React.useState(isMenuCollapsed);
102100

103101
// Auto open the details element, necessary when navigating from the index page
104102
React.useEffect(() => {
@@ -118,10 +116,7 @@ function MenuCategoryDetails({
118116
// of useIsActivePath
119117
setIsOpen(open);
120118

121-
submit(
122-
{ name: slug!, open: String(open) },
123-
{ navigate: false, method: "post", action: "/_sidebar" }
124-
);
119+
submitMenuCollapse(open);
125120
}}
126121
>
127122
{children}

Diff for: app/modules/menu-collapse.server.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
createCookieSessionStorage,
3+
type MiddlewareFunctionArgs,
4+
type RouterContext,
5+
type Session,
6+
} from "react-router";
7+
import { createContext, provide, pull } from "@ryanflorence/async-provider";
8+
9+
type MenuCollapseState = {
10+
"menu-collapse-state"?: Record<string, boolean>;
11+
};
12+
13+
let storage = createCookieSessionStorage<MenuCollapseState>({
14+
cookie: {
15+
name: "_menu-collapse-state",
16+
maxAge: 60 * 60 * 24 * 365,
17+
httpOnly: true,
18+
secure: process.env.NODE_ENV === "production",
19+
sameSite: "lax",
20+
},
21+
});
22+
23+
let menuCollapseStateContext = createContext<Session<MenuCollapseState>>();
24+
25+
export let menuCollapseStateMiddleware = async ({
26+
request,
27+
next,
28+
}: MiddlewareFunctionArgs<RouterContext, Response>) => {
29+
let cookieHeader = request.headers.get("Cookie");
30+
let session = await storage.getSession(cookieHeader);
31+
// Setting the cookie and wrapping the response in the context
32+
return provide([[menuCollapseStateContext, session]], async () => {
33+
try {
34+
let res = await next();
35+
res.headers.append("Set-Cookie", await storage.commitSession(session));
36+
return res;
37+
} catch (e) {
38+
console.log("session middleware error", request.url);
39+
console.log(e);
40+
return new Response("Oops, something went wrong.", { status: 500 });
41+
}
42+
});
43+
};
44+
45+
export function setMenuCollapseState(category: string, value: boolean) {
46+
let session = pull(menuCollapseStateContext);
47+
let state = session.get("menu-collapse-state") || {};
48+
state[category] = value;
49+
session.set("menu-collapse-state", state);
50+
}
51+
52+
export function getMenuCollapseState() {
53+
let session = pull(menuCollapseStateContext);
54+
let state = session.get("menu-collapse-state") || {};
55+
return state;
56+
}

Diff for: app/modules/sidebar-state.server.ts

-55
This file was deleted.

Diff for: app/pages/doc.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { CACHE_CONTROL } from "~/http";
33
import { seo } from "~/seo";
44
import semver from "semver";
55

6-
import { type HeadersArgs } from "react-router";
7-
86
import { getDocTitle, getDocsSearch, getRobots } from "~/ui/meta";
97
import { DocLayout } from "~/components/doc-layout";
108
import type { Route } from "./+types/doc";
@@ -39,14 +37,13 @@ export let loader = async ({ request, params }: Route.LoaderArgs) => {
3937
if (!doc) {
4038
throw new Response("Not Found", { status: 404 });
4139
}
42-
4340
return { doc };
4441
} catch (_) {
4542
throw new Response("Not Found", { status: 404 });
4643
}
4744
};
4845

49-
export function headers({ parentHeaders }: HeadersArgs) {
46+
export function headers({ parentHeaders }: Route.HeadersArgs) {
5047
parentHeaders.set("Cache-Control", CACHE_CONTROL.doc);
5148
return parentHeaders;
5249
}

Diff for: app/pages/docs-layout.tsx

+2-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Outlet, useRouteLoaderData } from "react-router";
1+
import { Outlet } from "react-router";
22
import classNames from "classnames";
33

44
import { Header } from "~/components/docs-header/docs-header";
@@ -12,13 +12,6 @@ import type { Route } from "./+types/docs-layout";
1212
import semver from "semver";
1313
import { useRef } from "react";
1414
import { useCodeBlockCopyButton } from "~/ui/utils";
15-
import {
16-
getSidebarState,
17-
sidebarSessionMiddleware,
18-
} from "~/modules/sidebar-state.server";
19-
import invariant from "tiny-invariant";
20-
21-
export let middleware = [sidebarSessionMiddleware];
2215

2316
export async function loader({ params }: Route.LoaderArgs) {
2417
let splat = params["*"];
@@ -38,9 +31,7 @@ export async function loader({ params }: Route.LoaderArgs) {
3831
getHeaderData("en", ref, refParam),
3932
]);
4033

41-
const sidebarState = getSidebarState();
42-
43-
return { menu, header, sidebarState };
34+
return { menu, header };
4435
}
4536

4637
export default function DocsLayout({ loaderData }: Route.ComponentProps) {
@@ -82,12 +73,3 @@ export default function DocsLayout({ loaderData }: Route.ComponentProps) {
8273
</div>
8374
);
8475
}
85-
86-
export function useSidebarState(slug: string) {
87-
const loaderData =
88-
useRouteLoaderData<Route.ComponentProps["loaderData"]>("docs");
89-
90-
invariant(loaderData, "No loader data found");
91-
92-
return loaderData.sidebarState[slug] ?? true;
93-
}

Diff for: app/root.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,29 @@ import {
1717
import { isHost } from "./modules/http-utils/is-host";
1818
import iconsHref from "~/icons.svg";
1919
import { DocSearch } from "./modules/docsearch";
20+
import type { Route } from "./+types/root";
21+
import {
22+
getMenuCollapseState,
23+
menuCollapseStateMiddleware,
24+
} from "./modules/menu-collapse.server";
2025

2126
import "~/styles/tailwind.css";
2227
import "@docsearch/css/dist/style.css";
2328
import "~/styles/docsearch.css";
2429
// FIXUP: Styles need to all be imported in root until this is fixed:
2530
// https://github.com/remix-run/react-router/issues/12382
2631
import "~/styles/docs.css";
27-
import type { Route } from "./+types/root";
32+
33+
export let middleware = [menuCollapseStateMiddleware];
2834

2935
export async function loader({ request }: LoaderFunctionArgs) {
3036
await middlewares(request);
3137

3238
let colorScheme = await parseColorScheme(request);
3339
let isProductionHost = isHost("reactrouter.com", request);
40+
const menuCollapseState = getMenuCollapseState();
3441

35-
return { colorScheme, isProductionHost };
42+
return { colorScheme, isProductionHost, menuCollapseState };
3643
}
3744

3845
export function headers() {

Diff for: app/routes.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ const routes: RouteConfig = [
55
route("/brand", "pages/brand.tsx"),
66
route("/healthcheck", "pages/healthcheck.tsx"),
77
route("/color-scheme", "actions/color-scheme.ts"),
8+
// Update the menu collapse session
9+
route("_update-menu-collapse", "api/update-menu-collapse.ts"),
810

911
route("", "pages/docs-layout.tsx", { id: "docs" }, [
1012
route("home", "pages/doc.tsx", { id: "home" }),
1113
route("*", "pages/doc.tsx"),
12-
route("_sidebar", "api/sidebar.ts"),
1314
]),
1415

1516
route("/:ref", "pages/docs-index.tsx", { id: "docs-index" }),

0 commit comments

Comments
 (0)