react-router-theme
Version:
Theme support for React Router and Remix
85 lines (64 loc) • 2.68 kB
text/typescript
import { useEffect, useState } from "react";
/**
* See Readme: https://www.npmjs.com/package/react-router-theme
*/
export const useTheme = (loaderData: any, fetcher: any, defaultTheme?: string) => {
let initialTheme: string;
if (!loaderData || !("theme" in loaderData)) {
throw new Error("Provided loader data does not contain theme.");
}
if (typeof loaderData.theme === "string") {
initialTheme = loaderData.theme;
} else if (loaderData.theme === null) {
initialTheme = defaultTheme ?? "default";
} else {
throw new Error("Provider loader data contains an invalid value for theme.");
}
const [theme, setTheme] = useState<string>(initialTheme);
const changeTheme = (theme: string) => {
setTheme(theme);
localStorage.setItem("theme", theme);
fetcher.submit({ action: "themeChange", theme: theme }, { method: "POST" });
};
// Sync theme updates across windows and tabs using local storage
const localStorageEventHandler = (event: StorageEvent) => {
if (event.key !== "theme" || event.oldValue === event.newValue || event.newValue === null) return;
setTheme(event.newValue);
};
useEffect(() => {
window.addEventListener("storage", localStorageEventHandler);
return () => window.removeEventListener("storage", localStorageEventHandler);
}, []);
return [theme, changeTheme] as const;
};
/**
* @param req incoming request in loader
* @returns the value of the theme cookie if found, otherwise NULL
*/
export const getThemeFromCookie = (req: Request) => {
const cookieHeader = req.headers.get("Cookie");
if (!cookieHeader) return null;
const themeMatch = cookieHeader.match(/theme=([^;]+)/);
if (!themeMatch) return null;
return themeMatch[1];
};
export const createThemeCookie = (formData: FormData) => {
const theme = formData.get("theme");
if (!theme) throw new Error("No theme specified in action form data");
return `theme=${theme}; Path=/; Max-Age=31536000`;
};
export const loader = async (args: { request: Request }) => {
return { theme: getThemeFromCookie(args.request) };
};
export const action = async (args: { request: Request }) => {
const formData = await args.request.formData();
if (formData.get("action") === "themeChange") return themeCookieResponse(formData);
return new Response("Unknown action. Create a custom action function to handle non-theme related requests.", { status: 500 });
};
export const themeCookieResponse = (formData: FormData) => {
return new Response(null, {
headers: {
"Set-Cookie": createThemeCookie(formData),
},
});
};