Skip to content

Commit

Permalink
ensure TransitionRoot component without props transitions correctly
Browse files Browse the repository at this point in the history
A bit of a weird one, but you can use the `TransitionRoot` component
without any props for transitions themselves (so no real transition can
happen). Even crazier, it can happen that it doesn't even render a DOM
node, but just its children.

At this point, the `TransitionRoot` component is purely there for
orchestration purposes of child components.

Since there is no DOM node in certain situations, the transitions (and
its `onStart` and `onStop` callbacks) won't even happen at all. This
causes a bug (obvious in react strict mode) where children don't
properly mount or the transition component doesn't properly unmount.
  • Loading branch information
RobinMalfait committed Apr 26, 2024
1 parent 539c124 commit 46aa359
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 13 deletions.
Expand Up @@ -559,6 +559,7 @@ function TransitionRootFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_C
let [state, setState] = useState(show ? TreeStates.Visible : TreeStates.Hidden)

let nestingBag = useNesting(() => {
if (show) return
setState(TreeStates.Hidden)
})

Expand Down Expand Up @@ -590,7 +591,7 @@ function TransitionRootFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_C
useIsoMorphicEffect(() => {
if (show) {
setState(TreeStates.Visible)
} else if (!hasChildren(nestingBag)) {
} else if (!hasChildren(nestingBag) && internalTransitionRef.current !== null) {
setState(TreeStates.Hidden)
}
}, [show, nestingBag])
Expand Down
32 changes: 20 additions & 12 deletions packages/@headlessui-react/src/hooks/use-transition.ts
Expand Up @@ -35,23 +35,31 @@ export function useTransition({ container, direction, classes, onStart, onStop }
let inFlight = useRef(false)

useIsoMorphicEffect(() => {
let node = container.current
if (!node) return // We don't have a DOM node (yet)
if (direction === 'idle') return // We don't need to transition
if (!mounted.current) return

onStart.current(direction)

d.add(
transition(node, {
direction,
classes: classes.current,
inFlight,
done() {
onStop.current(direction)
},
})
)
let node = container.current
if (!node) {
// No node, so let's skip the transition and call the `onStop` callback
// immediately because there is no transition to wait for anyway.
onStop.current(direction)
}

// We do have a node, let's transition it!
else {
d.add(
transition(node, {
direction,
classes: classes.current,
inFlight,
done() {
onStop.current(direction)
},
})
)
}

return d.dispose
}, [direction])
Expand Down

0 comments on commit 46aa359

Please sign in to comment.