|
1 | 1 | import { useControlledState, useEvent } from '@rc-component/util'; |
2 | | -import { useRef } from 'react'; |
| 2 | +import { useRef, useState, useEffect } from 'react'; |
3 | 3 |
|
4 | 4 | const internalMacroTask = (fn: VoidFunction) => { |
5 | 5 | const channel = new MessageChannel(); |
@@ -29,14 +29,27 @@ export type TriggerOpenType = (nextOpen?: boolean, ignoreNext?: boolean) => void |
29 | 29 | * Otherwise use uncontrolled logic. |
30 | 30 | * Setting `open` takes effect immediately, |
31 | 31 | * but setting it to `false` is delayed via MessageChannel. |
| 32 | + * |
| 33 | + * SSR handling: During SSR, `open` is always false to avoid Portal issues. |
| 34 | + * On client-side hydration, it syncs with the actual open state. |
32 | 35 | */ |
33 | 36 | export default function useOpen( |
34 | 37 | propOpen: boolean, |
35 | 38 | onOpen: (nextOpen: boolean) => void, |
36 | 39 | postOpen: (nextOpen: boolean) => boolean, |
37 | 40 | ): [boolean, TriggerOpenType] { |
| 41 | + // SSR not support Portal which means we need delay `open` for the first time render |
| 42 | + const [rendered, setRendered] = useState(false); |
| 43 | + |
| 44 | + useEffect(() => { |
| 45 | + setRendered(true); |
| 46 | + }, []); |
| 47 | + |
38 | 48 | const [stateOpen, internalSetOpen] = useControlledState(false, propOpen); |
39 | | - const mergedOpen = postOpen(stateOpen); |
| 49 | + |
| 50 | + // During SSR, always return false for open state |
| 51 | + const ssrSafeOpen = rendered ? stateOpen : false; |
| 52 | + const mergedOpen = postOpen(ssrSafeOpen); |
40 | 53 |
|
41 | 54 | const taskIdRef = useRef(0); |
42 | 55 | const taskLockRef = useRef(false); |
|
0 commit comments