Skip to content

Instantly share code, notes, and snippets.

@gragland
Last active February 24, 2023 17:20
Show Gist options
  • Save gragland/cdaab58e8621be22301700e6a5d59498 to your computer and use it in GitHub Desktop.
Save gragland/cdaab58e8621be22301700e6a5d59498 to your computer and use it in GitHub Desktop.
// Usage
function App() {
const [darkMode, setDarkMode] = useDarkMode();
return (
<div>
<div className="navbar">
<Toggle darkMode={darkMode} setDarkMode={setDarkMode} />
</div>
<Content />
</div>
);
}
// Hook
function useDarkMode() {
// Use our useLocalStorage hook to persist state through a page refresh.
// Read the recipe for this hook to learn more: usehooks.com/useLocalStorage
const [enabledState, setEnabledState] = useLocalStorage('dark-mode-enabled');
// See if user has set a browser or OS preference for dark mode.
// The usePrefersDarkMode hook composes a useMedia hook (see code below).
const prefersDarkMode = usePrefersDarkMode();
// If enabledState is defined use it, otherwise fallback to prefersDarkMode.
// This allows user to override OS level setting on our website.
const enabled =
typeof enabledState !== 'undefined' ? enabledState : prefersDarkMode;
// Fire off effect that add/removes dark mode class
useEffect(
() => {
const className = 'dark-mode';
const element = window.document.body;
if (enabled) {
element.classList.add(className);
} else {
element.classList.remove(className);
}
},
[enabled] // Only re-call effect when value changes
);
// Return enabled state and setter
return [enabled, setEnabledState];
}
// Compose our useMedia hook to detect dark mode preference.
// The API for useMedia looks a bit weird, but that's because ...
// ... it was designed to support multiple media queries and return values.
// Thanks to hook composition we can hide away that extra complexity!
// Read the recipe for useMedia to learn more: usehooks.com/useMedia
function usePrefersDarkMode() {
return useMedia(['(prefers-color-scheme: dark)'], [true], false);
}
@gragland
Copy link
Author

@DimitarNestorov Thanks, changed it to window and moved into useEffect callback to avoid SSR issues with window not being defined. What is the rationale behind wrapping the callback in requestAnimationFrame? Since this effect fires infrequently seems like an unnecessary performance optimization to me.

@dustinmyers
Copy link

This is small but will help users get in the right mindset of effect hooks. I think the comment // Only re-call effect when value changes should say something to like // Sync this effect with the "enabled" state. This comes with the explanation of the new mental model behind the effect hook by Ryan Florence - https://twitter.com/ryanflorence/status/1125041041063665666

@lesleh
Copy link

lesleh commented Feb 24, 2023

This code:

if (enabled) {
  element.classList.add(className);
} else {
  element.classList.remove(className);
}

can be simplified to:

element.classList.toggle(className, enabled);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment