2022-06-08 13:26:57 +02:00
|
|
|
import React, { Children, useEffect, useLayoutEffect } from 'react'
|
2019-01-25 01:39:15 +01:00
|
|
|
|
2020-06-12 00:09:06 +02:00
|
|
|
type State = JSX.Element[] | undefined
|
2019-01-25 01:39:15 +01:00
|
|
|
|
|
|
|
type SideEffectProps = {
|
2019-05-30 03:19:32 +02:00
|
|
|
reduceComponentsToState: <T>(
|
|
|
|
components: Array<React.ReactElement<any>>,
|
|
|
|
props: T
|
|
|
|
) => State
|
|
|
|
handleStateChange?: (state: State) => void
|
2020-06-12 00:09:06 +02:00
|
|
|
headManager: any
|
2019-06-27 16:22:24 +02:00
|
|
|
inAmpMode?: boolean
|
2022-06-08 13:26:57 +02:00
|
|
|
children: React.ReactNode
|
2019-01-25 01:39:15 +01:00
|
|
|
}
|
|
|
|
|
2022-06-08 13:26:57 +02:00
|
|
|
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(
|
2022-07-02 03:59:04 +02:00
|
|
|
Array.from(headManager.mountedInstances as Set<unknown>).filter(Boolean)
|
|
|
|
) as React.ReactElement[]
|
2022-06-08 13:26:57 +02:00
|
|
|
headManager.updateHead(reduceComponentsToState(headElements, props))
|
2020-06-23 22:46:40 +02:00
|
|
|
}
|
2019-01-25 01:39:15 +01:00
|
|
|
}
|
|
|
|
|
2022-06-08 13:26:57 +02:00
|
|
|
if (isServer) {
|
|
|
|
headManager?.mountedInstances?.add(props.children)
|
|
|
|
emitChange()
|
|
|
|
}
|
2020-06-23 22:46:40 +02:00
|
|
|
|
2022-06-08 13:26:57 +02:00
|
|
|
useClientOnlyLayoutEffect(() => {
|
|
|
|
headManager?.mountedInstances?.add(props.children)
|
|
|
|
return () => {
|
|
|
|
headManager?.mountedInstances?.delete(props.children)
|
2019-01-25 01:39:15 +01:00
|
|
|
}
|
2022-06-08 13:26:57 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
// 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
|
2020-06-23 22:46:40 +02:00
|
|
|
}
|
2022-06-08 13:26:57 +02:00
|
|
|
return () => {
|
|
|
|
if (headManager) {
|
|
|
|
headManager._pendingUpdate = emitChange
|
|
|
|
}
|
2020-06-23 22:46:40 +02:00
|
|
|
}
|
2022-06-08 13:26:57 +02:00
|
|
|
})
|
2019-01-25 01:39:15 +01:00
|
|
|
|
2022-06-08 13:26:57 +02:00
|
|
|
useClientOnlyEffect(() => {
|
|
|
|
if (headManager && headManager._pendingUpdate) {
|
|
|
|
headManager._pendingUpdate()
|
|
|
|
headManager._pendingUpdate = null
|
|
|
|
}
|
|
|
|
return () => {
|
|
|
|
if (headManager && headManager._pendingUpdate) {
|
|
|
|
headManager._pendingUpdate()
|
|
|
|
headManager._pendingUpdate = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return null
|
2019-01-25 01:39:15 +01:00
|
|
|
}
|