Skip to content

Commit

Permalink
refactor(theme-provider): improve theme management with `useSyncExter…
Browse files Browse the repository at this point in the history
…nalStore`
  • Loading branch information
juliomalves committed Jan 5, 2025
1 parent 28dd8ec commit 72d3e8e
Showing 1 changed file with 33 additions and 18 deletions.
51 changes: 33 additions & 18 deletions components/contexts/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,44 @@ interface IThemeProvider {

export const useTheme = () => React.useContext<IThemeContext>(ThemeContext)

export const ThemeProvider = ({ storageKey = STORAGE_KEY, children }: React.PropsWithChildren<IThemeProvider> = {}) => {
const [theme, setTheme] = React.useState(() => {
if (typeof window === 'undefined') {
return 'light'
}
const storedMode = window.localStorage.getItem(storageKey) as Theme
return !storedMode ? (window.matchMedia(MEDIA_QUERY).matches ? 'dark' : 'light') : storedMode
})
const subscribeToLocalStorage = (callback: () => void) => {
window.addEventListener('storage', callback)
return () => {
window.removeEventListener('storage', callback)
}
}

const toggleTheme = () => setTheme(theme === 'dark' ? 'light' : 'dark')
const subscribeToMediaQuery = (callback: () => void) => {
const mediaQueryList = window.matchMedia(MEDIA_QUERY)
mediaQueryList.addEventListener('change', callback)
return () => {
mediaQueryList.removeEventListener('change', callback)
}
}

React.useEffect(() => {
const mediaQueryList = window.matchMedia(MEDIA_QUERY)
const handleChange = (ev: MediaQueryListEvent) => setTheme(ev.matches ? 'dark' : 'light')
mediaQueryList.addEventListener('change', handleChange)
return () => mediaQueryList.removeEventListener('change', handleChange)
}, [])
export const ThemeProvider = ({ storageKey = STORAGE_KEY, children }: React.PropsWithChildren<IThemeProvider> = {}) => {
const themeFromLocalStorage = React.useSyncExternalStore(
subscribeToLocalStorage,
() => localStorage.getItem(storageKey) as Theme | null,
() => null
)
const themeFromMediaQuery = React.useSyncExternalStore(
subscribeToMediaQuery,
() => (window.matchMedia(MEDIA_QUERY).matches ? 'dark' : 'light') as Theme,
() => 'light' as Theme
)
const theme = themeFromLocalStorage ?? themeFromMediaQuery

const toggleTheme = () => {
const newTheme = theme === 'dark' ? 'light' : 'dark'
localStorage.setItem(storageKey, newTheme)
window.dispatchEvent(new Event('storage'))
}

React.useEffect(() => {
window.localStorage.setItem(storageKey, theme)
document.documentElement.classList.add(theme)
document.documentElement.classList.remove(theme === 'dark' ? 'light' : 'dark')
}, [theme, storageKey])
}, [theme])

return <ThemeContext.Provider value={{ toggleTheme, theme }}>{children}</ThemeContext.Provider>
return <ThemeContext value={{ toggleTheme, theme }}>{children}</ThemeContext>
}

0 comments on commit 72d3e8e

Please sign in to comment.