Files
headlessui/packages/@headlessui-react/src/utils/stable-collection.tsx
T
Jordan Pittman e95f664a36 Fix SSR tab hydration when using Strict Mode in development (#2231)
* Work on SSR tests for react

* Use React internals to count tabs and panels

React’s double rendering in strict mode in development makes SSR + hydration matching impossible without reaching into internals. This is unfortunate but the way react works. Production builds of React are unaffected by this but still require a consistent mechanism that works so in that case we use Symbols just like we do in SSR.

* Update changelog
2023-01-31 15:40:13 -05:00

82 lines
2.0 KiB
TypeScript

import * as React from 'react'
type CollectionKey = string | symbol
type CollectionItem = [number, () => void]
type CollectionRef = React.MutableRefObject<ReturnType<typeof createCollection>>
const StableCollectionContext = React.createContext<CollectionRef | null>(null)
function createCollection() {
return {
/** @type {Map<string, Map<string, number>>} */
groups: new Map(),
get(group: string, key: CollectionKey): CollectionItem {
let list = this.groups.get(group)
if (!list) {
list = new Map()
this.groups.set(group, list)
}
let renders = list.get(key) ?? 0
list.set(key, renders + 1)
let index = Array.from(list.keys()).indexOf(key)
function release() {
let renders = list.get(key)
if (renders > 1) {
list.set(key, renders - 1)
} else {
list.delete(key)
}
}
return [index, release]
},
}
}
export function StableCollection({ children }: { children: React.ReactNode | React.ReactNode[] }) {
let collection = React.useRef(createCollection())
return (
<StableCollectionContext.Provider value={collection}>
{children}
</StableCollectionContext.Provider>
)
}
export function useStableCollectionIndex(group: string) {
let collection = React.useContext(StableCollectionContext)
if (!collection) throw new Error('You must wrap your component in a <StableCollection>')
let key = useStableCollectionKey()
let [idx, cleanupIdx] = collection.current.get(group, key)
React.useEffect(() => cleanupIdx, [])
return idx
}
/**
* Return a stable key based on the position of this node.
*
* @returns {symbol | string}
*/
function useStableCollectionKey() {
let owner =
// @ts-ignore
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?.ReactCurrentOwner?.current ?? null
// ssr: dev/prod
// client: prod
if (!owner) return Symbol()
// client: dev
let indexes = []
let fiber = owner
while (fiber) {
indexes.push(fiber.index)
fiber = fiber.return
}
return '$.' + indexes.join('.')
}