From 795b92cc1a168c48c7710ca6e0e1ef5974013d95 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Thu, 26 Oct 2023 19:07:31 +0200
Subject: refactor(hooks): rewrite useLocalStorage hook

* return a tuple instead of an object
* add a validator function as parameter (if the stored value
is manually changed, it is not safe to cast its type)
* add tests
---
 src/utils/providers/prism-theme.tsx | 61 +++++++++++++++++++++----------------
 1 file changed, 35 insertions(+), 26 deletions(-)

(limited to 'src/utils/providers/prism-theme.tsx')

diff --git a/src/utils/providers/prism-theme.tsx b/src/utils/providers/prism-theme.tsx
index c063bd5..603e8a7 100644
--- a/src/utils/providers/prism-theme.tsx
+++ b/src/utils/providers/prism-theme.tsx
@@ -1,11 +1,13 @@
+/* eslint-disable max-statements */
 import {
   createContext,
-  FC,
-  ReactNode,
+  type FC,
+  type ReactNode,
   useCallback,
   useContext,
   useEffect,
   useState,
+  useMemo,
 } from 'react';
 import { useAttributes, useLocalStorage, useQuerySelectorAll } from '../hooks';
 
@@ -42,12 +44,9 @@ export const usePrismTheme = () => useContext(PrismThemeContext);
  * @returns {boolean|undefined} True if `prefers-color-scheme` is set to `dark`.
  */
 const prefersDarkScheme = (): boolean | undefined => {
-  if (typeof window === 'undefined') return;
+  if (typeof window === 'undefined') return undefined;
 
-  return (
-    window.matchMedia &&
-    window.matchMedia('(prefers-color-scheme: dark)').matches
-  );
+  return window.matchMedia('(prefers-color-scheme: dark)').matches;
 };
 
 /**
@@ -56,25 +55,33 @@ const prefersDarkScheme = (): boolean | undefined => {
  * @param {string} theme - A string.
  * @returns {boolean} True if the given string match a Prism theme name.
  */
-const isValidTheme = (theme: string): boolean => {
-  return theme === 'dark' || theme === 'light' || theme === 'system';
-};
+const isValidTheme = (theme: string): boolean =>
+  theme === 'dark' || theme === 'light' || theme === 'system';
+
+const defaultThemes = ['dark', 'light', 'system'] satisfies PrismTheme[];
+
+const validator = (value: unknown): value is PrismTheme =>
+  typeof value === 'string' && (defaultThemes as string[]).includes(value);
 
 export const PrismThemeProvider: FC<PrismThemeProviderProps> = ({
   attribute = 'data-prismjs-color-scheme-current',
   storageKey = 'prismjs-color-scheme',
-  themes = ['dark', 'light', 'system'],
+  themes = defaultThemes,
   children,
 }) => {
   /**
    * Retrieve the theme to use depending on `prefers-color-scheme`.
    */
   const getThemeFromSystem = useCallback(() => {
-    return prefersDarkScheme() ? 'dark' : 'light';
+    if (prefersDarkScheme()) return 'dark';
+    return 'light';
   }, []);
 
-  const { value: prismTheme, setValue: setPrismTheme } =
-    useLocalStorage<PrismTheme>(storageKey, 'system');
+  const [prismTheme, setPrismTheme] = useLocalStorage<PrismTheme>(
+    storageKey,
+    'system',
+    validator
+  );
 
   useEffect(() => {
     if (!isValidTheme(prismTheme)) setPrismTheme('system');
@@ -113,10 +120,10 @@ export const PrismThemeProvider: FC<PrismThemeProviderProps> = ({
    */
   const listenAttributeChange = useCallback(
     (pre: HTMLPreElement) => {
-      var observer = new MutationObserver(function (mutations) {
+      const observer = new MutationObserver((mutations) => {
         mutations.forEach((record) => {
-          var mutatedPre = record.target as HTMLPreElement;
-          var newTheme = mutatedPre.getAttribute(attribute) as PrismTheme;
+          const mutatedPre = record.target as HTMLPreElement;
+          const newTheme = mutatedPre.getAttribute(attribute) as PrismTheme;
           setPrismTheme(newTheme);
         });
       });
@@ -133,16 +140,18 @@ export const PrismThemeProvider: FC<PrismThemeProviderProps> = ({
     preTags.forEach(listenAttributeChange);
   }, [preTags, listenAttributeChange]);
 
+  const value = useMemo(() => {
+    return {
+      themes,
+      theme: prismTheme,
+      setTheme: setPrismTheme,
+      codeBlocks: preTags,
+      resolvedTheme,
+    };
+  }, [preTags, prismTheme, resolvedTheme, setPrismTheme, themes]);
+
   return (
-    <PrismThemeContext.Provider
-      value={{
-        themes,
-        theme: prismTheme,
-        setTheme: setPrismTheme,
-        codeBlocks: preTags,
-        resolvedTheme,
-      }}
-    >
+    <PrismThemeContext.Provider value={value}>
       {children}
     </PrismThemeContext.Provider>
   );
-- 
cgit v1.2.3

