Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve color scheme store perf #8059

Merged
merged 1 commit into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"check:deps": "pnpm --recursive --parallel exec depcheck",
"check:format": "prettier . --check",
"check:lint": "turbo run lint --continue -- --quiet",
"check:react-compiler": "eslint --cache --no-inline-config --no-eslintrc --ext .cjs,.mjs,.js,.jsx,.ts,.tsx --parser @typescript-eslint/parser --plugin react-compiler --rule 'react-compiler/react-compiler: [warn]' --ignore-path .eslintignore.react-compiler --max-warnings 77 .",
"check:react-compiler": "eslint --cache --no-inline-config --no-eslintrc --ext .cjs,.mjs,.js,.jsx,.ts,.tsx --parser @typescript-eslint/parser --plugin react-compiler --rule 'react-compiler/react-compiler: [warn]' --ignore-path .eslintignore.react-compiler --max-warnings 76 .",
"report:react-compiler-bailout": "eslint --cache --no-inline-config --no-eslintrc --ext .cjs,.mjs,.js,.jsx,.ts,.tsx --parser @typescript-eslint/parser --plugin react-compiler --rule 'react-compiler/react-compiler: [error,{__unstable_donotuse_reportAllBailouts:true}]' --ignore-path .eslintignore.react-compiler -f ./scripts/reactCompilerBailouts.cjs . || true",
"check:test": "run-s test -- --silent",
"check:types": "tsc && turbo run check:types --filter='./packages/*' --filter='./packages/@sanity/*'",
Expand Down
51 changes: 11 additions & 40 deletions packages/sanity/src/core/studio/colorScheme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {ColorSchemeSetValueContext, ColorSchemeValueContext} from 'sanity/_singl

import {type TFunction} from '../i18n'
import {type StudioThemeColorSchemeKey} from '../theme/types'
import {getSnapshot, setSnapshot, subscribe} from './colorSchemeStore'

/** @internal */
function useSystemScheme(): ThemeColorSchemeKey {
Expand Down Expand Up @@ -78,33 +79,13 @@ export function ColorSchemeLocalStorageProvider({
children,
onSchemeChange,
}: Pick<ColorSchemeProviderProps, 'children' | 'onSchemeChange'>) {
const store = useMemo(() => {
let snapshot: StudioThemeColorSchemeKey
const subscribers = new Set<() => void>()

return {
subscribe: (onStoreChange: () => void) => {
if (!snapshot) {
snapshot = getScheme(localStorage.getItem(LOCAL_STORAGE_KEY)) || 'system'
}
subscribers.add(onStoreChange)
return () => {
subscribers.delete(onStoreChange)
}
},
getSnapshot: () => snapshot,
setSnapshot: (nextScheme: StudioThemeColorSchemeKey) => {
snapshot = getScheme(nextScheme)
for (const subscription of subscribers) {
subscription()
}
},
// Only called during server-side rendering, and hydration if using hydrateRoot
// https://beta.reactjs.org/apis/react/useSyncExternalStore#adding-support-for-server-rendering
getServerSnapshot: () => 'system',
}
}, [])
const scheme = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)
const scheme = useSyncExternalStore<StudioThemeColorSchemeKey>(
subscribe,
getSnapshot,
// Only called during server-side rendering, and hydration if using hydrateRoot
// https://beta.reactjs.org/apis/react/useSyncExternalStore#adding-support-for-server-rendering
() => 'system',
)

useEffect(() => {
if (typeof onSchemeChange === 'function') {
Expand All @@ -114,24 +95,14 @@ export function ColorSchemeLocalStorageProvider({
}, [onSchemeChange, scheme])

return (
<ColorSchemeSetValueContext.Provider value={store.setSnapshot}>
<ColorSchemeSetValueContext.Provider value={setSnapshot}>
<ColorSchemeValueContext.Provider value={scheme}>
<ColorThemeProvider scheme={scheme}>{children}</ColorThemeProvider>
</ColorSchemeValueContext.Provider>
</ColorSchemeSetValueContext.Provider>
)
}

function getScheme(scheme: unknown): StudioThemeColorSchemeKey {
switch (scheme) {
case 'dark':
case 'light':
return scheme
default:
return 'system'
}
}

/**
* If the `scheme` prop is provided we don't need to setup any logic to handle localStorage
* @internal
Expand Down Expand Up @@ -210,7 +181,7 @@ export function useColorSchemeOptions(
) {
const scheme = useColorSchemeInternalValue()

return useMemo(() => {
return useMemo<ColorSchemeOption[]>(() => {
return [
{
title: t('user-menu.color-scheme.system-title'),
Expand All @@ -236,6 +207,6 @@ export function useColorSchemeOptions(
onSelect: () => setScheme('light'),
icon: SunIcon,
},
] satisfies ColorSchemeOption[]
]
}, [scheme, setScheme, t])
}
39 changes: 39 additions & 0 deletions packages/sanity/src/core/studio/colorSchemeStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {type StudioThemeColorSchemeKey} from '../theme/types'

function getScheme(scheme: unknown): StudioThemeColorSchemeKey {
switch (scheme) {
case 'dark':
case 'light':
return scheme
default:
return 'system'
}
}

/** @internal */
export const LOCAL_STORAGE_KEY = 'sanityStudio:ui:colorScheme'

let snapshot: StudioThemeColorSchemeKey
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Compiler doesn't like writing to ESM bindings like this one, during render.
That's fine, we can move it into a separate file like this and the compiler no longer complains, as it's out of scope :)

const subscribers = new Set<() => void>()

/** @internal */
export const subscribe = (onStoreChange: () => void) => {
if (!snapshot) {
snapshot = getScheme(localStorage.getItem(LOCAL_STORAGE_KEY)) || 'system'
}
subscribers.add(onStoreChange)
return (): void => {
subscribers.delete(onStoreChange)
}
}
/** @internal */
export function getSnapshot(): StudioThemeColorSchemeKey {
return snapshot
}
/** @internal */
export function setSnapshot(nextScheme: StudioThemeColorSchemeKey): void {
snapshot = getScheme(nextScheme)
for (const subscription of subscribers) {
subscription()
}
}
Loading