Skip to content

Commit 0cf2fbc

Browse files
committed
graduate primer_react_theme_provider_remove_ssr_handoff ff
1 parent cb79351 commit 0cf2fbc

3 files changed

Lines changed: 3 additions & 117 deletions

File tree

packages/react/src/FeatureFlags/DefaultFeatureFlags.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({
88
primer_react_action_list_group_heading_trailing_action: false,
99
primer_react_action_list_item_gap: false,
1010
primer_react_timeline_list_semantics: false,
11-
primer_react_theme_provider_remove_ssr_handoff: false,
1211
})

packages/react/src/ThemeProvider.tsx

Lines changed: 3 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React from 'react'
22
import defaultTheme from './theme'
33
import deepmerge from 'deepmerge'
4-
import {useId} from './hooks'
5-
import {useFeatureFlag} from './FeatureFlags'
64
import {useSyncedState} from './hooks/useSyncedState'
75
import {ThemeContext} from './ThemeContext'
86
import {useTheme} from './useTheme'
@@ -21,7 +19,7 @@ export type ThemeProviderProps = {
2119
dayScheme?: string
2220
nightScheme?: string
2321
/**
24-
* No-op when the `primer_react_theme_provider_remove_ssr_handoff` feature flag is enabled.
22+
* @deprecated This prop is no longer used and has no effect.
2523
*/
2624
preventSSRMismatch?: boolean
2725
/**
@@ -32,33 +30,6 @@ export type ThemeProviderProps = {
3230
contextOnly?: boolean
3331
}
3432

35-
// inspired from __NEXT_DATA__, we use application/json to avoid CSRF policy with inline scripts
36-
const serverHandoffCache = new Map<string, Record<string, unknown>>()
37-
const emptyHandoff: Record<string, unknown> = {}
38-
const getServerHandoff = (id: string) => {
39-
if (typeof document === 'undefined') return emptyHandoff
40-
41-
const cached = serverHandoffCache.get(id)
42-
if (cached !== undefined) return cached
43-
44-
try {
45-
const serverData = document.getElementById(`__PRIMER_DATA_${id}__`)?.textContent
46-
if (serverData) {
47-
const parsed = JSON.parse(serverData)
48-
serverHandoffCache.set(id, parsed)
49-
return parsed
50-
}
51-
} catch (_error) {
52-
// if document/element does not exist or JSON is invalid, suppress error
53-
}
54-
55-
const empty = {}
56-
serverHandoffCache.set(id, empty)
57-
return empty
58-
}
59-
60-
const emptySubscribe = () => () => {}
61-
6233
export const ThemeProvider: React.FC<React.PropsWithChildren<ThemeProviderProps>> = ({children, ...props}) => {
6334
// Get fallback values from parent ThemeProvider (if exists)
6435
const {
@@ -71,22 +42,11 @@ export const ThemeProvider: React.FC<React.PropsWithChildren<ThemeProviderProps>
7142
// Initialize state
7243
const theme = fallbackTheme ?? defaultTheme
7344

74-
const removeSSRHandoff = useFeatureFlag('primer_react_theme_provider_remove_ssr_handoff')
75-
const uniqueDataId = useId()
76-
7745
const [colorMode, setColorMode] = useSyncedState(props.colorMode ?? fallbackColorMode ?? defaultColorMode)
7846
const [dayScheme, setDayScheme] = useSyncedState(props.dayScheme ?? fallbackDayScheme ?? defaultDayScheme)
7947
const [nightScheme, setNightScheme] = useSyncedState(props.nightScheme ?? fallbackNightScheme ?? defaultNightScheme)
8048
const systemColorMode = useSystemColorMode()
81-
const clientColorMode = resolveColorMode(colorMode, systemColorMode)
82-
// During SSR/hydration, use the server-rendered color mode from the handoff script tag
83-
// to avoid mismatches. After hydration, resolve from client state.
84-
const ssrResolvedColorMode = React.useSyncExternalStore(
85-
emptySubscribe,
86-
() => clientColorMode,
87-
() => getServerHandoff(uniqueDataId).resolvedServerColorMode ?? clientColorMode,
88-
)
89-
const resolvedColorMode = removeSSRHandoff ? clientColorMode : ssrResolvedColorMode
49+
const resolvedColorMode = resolveColorMode(colorMode, systemColorMode)
9050
const colorScheme = chooseColorScheme(resolvedColorMode, dayScheme, nightScheme)
9151
const {resolvedTheme, resolvedColorScheme} = React.useMemo(
9252
() => applyColorScheme(theme, colorScheme),
@@ -120,22 +80,8 @@ export const ThemeProvider: React.FC<React.PropsWithChildren<ThemeProviderProps>
12080
],
12181
)
12282

123-
const ssrHandoffScript =
124-
!removeSSRHandoff && props.preventSSRMismatch ? (
125-
<script
126-
type="application/json"
127-
id={`__PRIMER_DATA_${uniqueDataId}__`}
128-
dangerouslySetInnerHTML={{__html: JSON.stringify({resolvedServerColorMode: resolvedColorMode})}}
129-
/>
130-
) : null
131-
13283
if (props.contextOnly) {
133-
return (
134-
<ThemeContext.Provider value={contextValue}>
135-
{children}
136-
{ssrHandoffScript}
137-
</ThemeContext.Provider>
138-
)
84+
return <ThemeContext.Provider value={contextValue}>{children}</ThemeContext.Provider>
13985
}
14086

14187
return (
@@ -146,7 +92,6 @@ export const ThemeProvider: React.FC<React.PropsWithChildren<ThemeProviderProps>
14692
data-dark-theme={nightScheme}
14793
>
14894
{children}
149-
{ssrHandoffScript}
15095
</div>
15196
</ThemeContext.Provider>
15297
)

packages/react/src/__tests__/ThemeProvider.test.tsx

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event'
33
import {describe, expect, it, vi} from 'vitest'
44
import React from 'react'
55
import ThemeProvider from '../ThemeProvider'
6-
import {FeatureFlags} from '../FeatureFlags'
76
import {useColorSchemeVar, useTheme} from '../useTheme'
87

98
// copied from '@primer/primitives/dist/css/functional/themes/';
@@ -571,61 +570,4 @@ describe('contextOnly', () => {
571570

572571
expect(screen.getByTestId('consumer').textContent).toBe('night-light-dark_dimmed')
573572
})
574-
575-
it('renders the preventSSRMismatch script tag when contextOnly and preventSSRMismatch are both true', () => {
576-
const {container} = render(
577-
<ThemeProvider contextOnly preventSSRMismatch>
578-
<span>Hello</span>
579-
</ThemeProvider>,
580-
)
581-
582-
const div = container.querySelector('[data-color-mode]')
583-
expect(div).not.toBeInTheDocument()
584-
585-
const script = container.querySelector('script[type="application/json"]')
586-
expect(script).toBeInTheDocument()
587-
expect(script?.textContent).toContain('resolvedServerColorMode')
588-
})
589-
})
590-
591-
describe('primer_react_theme_provider_remove_ssr_handoff feature flag', () => {
592-
it('does not render the script tag when the feature flag is enabled', () => {
593-
const {container} = render(
594-
<FeatureFlags flags={{primer_react_theme_provider_remove_ssr_handoff: true}}>
595-
<ThemeProvider preventSSRMismatch>
596-
<span>Hello</span>
597-
</ThemeProvider>
598-
</FeatureFlags>,
599-
)
600-
601-
const script = container.querySelector('script[type="application/json"]')
602-
expect(script).not.toBeInTheDocument()
603-
})
604-
605-
it('does not render the script tag when the feature flag is enabled and contextOnly is true', () => {
606-
const {container} = render(
607-
<FeatureFlags flags={{primer_react_theme_provider_remove_ssr_handoff: true}}>
608-
<ThemeProvider contextOnly preventSSRMismatch>
609-
<span>Hello</span>
610-
</ThemeProvider>
611-
</FeatureFlags>,
612-
)
613-
614-
const script = container.querySelector('script[type="application/json"]')
615-
expect(script).not.toBeInTheDocument()
616-
})
617-
618-
it('renders the script tag when the feature flag is disabled', () => {
619-
const {container} = render(
620-
<FeatureFlags flags={{primer_react_theme_provider_remove_ssr_handoff: false}}>
621-
<ThemeProvider preventSSRMismatch>
622-
<span>Hello</span>
623-
</ThemeProvider>
624-
</FeatureFlags>,
625-
)
626-
627-
const script = container.querySelector('script[type="application/json"]')
628-
expect(script).toBeInTheDocument()
629-
expect(script?.textContent).toContain('resolvedServerColorMode')
630-
})
631573
})

0 commit comments

Comments
 (0)