rsnext/packages/next/shared/lib/side-effect.tsx
Jiachi Liu d6090178fe
Fix: convert head instances to array (#38252)
* fix: convert head instances to array

* fix test
2022-07-01 20:59:04 -05:00

74 lines
2.2 KiB
TypeScript

import React, { Children, useEffect, useLayoutEffect } from 'react'
type State = JSX.Element[] | undefined
type SideEffectProps = {
reduceComponentsToState: <T>(
components: Array<React.ReactElement<any>>,
props: T
) => State
handleStateChange?: (state: State) => void
headManager: any
inAmpMode?: boolean
children: React.ReactNode
}
const isServer = typeof window === 'undefined'
const useClientOnlyLayoutEffect = isServer ? () => {} : useLayoutEffect
const useClientOnlyEffect = isServer ? () => {} : useEffect
export default function SideEffect(props: SideEffectProps) {
const { headManager, reduceComponentsToState } = props
function emitChange() {
if (headManager && headManager.mountedInstances) {
const headElements = Children.toArray(
Array.from(headManager.mountedInstances as Set<unknown>).filter(Boolean)
) as React.ReactElement[]
headManager.updateHead(reduceComponentsToState(headElements, props))
}
}
if (isServer) {
headManager?.mountedInstances?.add(props.children)
emitChange()
}
useClientOnlyLayoutEffect(() => {
headManager?.mountedInstances?.add(props.children)
return () => {
headManager?.mountedInstances?.delete(props.children)
}
})
// We need to call `updateHead` method whenever the `SideEffect` is trigger in all
// life-cycles: mount, update, unmount. However, if there are multiple `SideEffect`s
// being rendered, we only trigger the method from the last one.
// This is ensured by keeping the last unflushed `updateHead` in the `_pendingUpdate`
// singleton in the layout effect pass, and actually trigger it in the effect pass.
useClientOnlyLayoutEffect(() => {
if (headManager) {
headManager._pendingUpdate = emitChange
}
return () => {
if (headManager) {
headManager._pendingUpdate = emitChange
}
}
})
useClientOnlyEffect(() => {
if (headManager && headManager._pendingUpdate) {
headManager._pendingUpdate()
headManager._pendingUpdate = null
}
return () => {
if (headManager && headManager._pendingUpdate) {
headManager._pendingUpdate()
headManager._pendingUpdate = null
}
}
})
return null
}