Skip to content

Commit

Permalink
Memoize ThemeContext.Provider value (pacocoursey#135)
Browse files Browse the repository at this point in the history
Every time <Theme /> renders a new object is constructed and passed to ThemeContext.Provider. This guarantees that regardless of what inputs changed the Context will be propagated. This is particularly harmful when React is doing hydration because if an unhydrated Suspense boudnary exists in the sub-tree of the Provider it will fall back to client rendering regardless of whether the context is an actual dependency for that Suspense boundary.

This commit adds memoization so the value only changes if one of it's inputs change
To make this memoization effective the default argument for `themes` needed to be statically extracted (it constructs a new array on each function invocation otherwise)
  • Loading branch information
gnoff authored Sep 8, 2022
1 parent 4e819f7 commit 63f7005
Showing 1 changed file with 14 additions and 9 deletions.
23 changes: 14 additions & 9 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React, {
useContext,
useEffect,
useState,
useMemo,
memo
} from 'react'
import type { UseThemeProps, ThemeProviderProps } from './types'
Expand All @@ -25,13 +26,15 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = props => {
return <Theme {...props} />
}

const defaultThemes = ['light', 'dark'];

const Theme: React.FC<ThemeProviderProps> = ({
forcedTheme,
disableTransitionOnChange = false,
enableSystem = true,
enableColorScheme = true,
storageKey = 'theme',
themes = ['light', 'dark'],
themes = defaultThemes,
defaultTheme = enableSystem ? 'system' : 'light',
attribute = 'data-theme',
value,
Expand Down Expand Up @@ -135,16 +138,18 @@ const Theme: React.FC<ThemeProviderProps> = ({
applyTheme(forcedTheme ?? theme)
}, [forcedTheme, theme])

const providerValue = useMemo(() => ({
theme,
setTheme,
forcedTheme,
resolvedTheme: theme === 'system' ? resolvedTheme : theme,
themes: enableSystem ? [...themes, 'system'] : themes,
systemTheme: (enableSystem ? resolvedTheme : undefined) as 'light' | 'dark' | undefined
}), [theme, setTheme, forcedTheme, resolvedTheme, enableSystem, themes]);

return (
<ThemeContext.Provider
value={{
theme,
setTheme,
forcedTheme,
resolvedTheme: theme === 'system' ? resolvedTheme : theme,
themes: enableSystem ? [...themes, 'system'] : themes,
systemTheme: (enableSystem ? resolvedTheme : undefined) as 'light' | 'dark' | undefined
}}
value={providerValue}
>
<ThemeScript
{...{
Expand Down

0 comments on commit 63f7005

Please sign in to comment.