Skip to content

Commit

Permalink
Ensure TransitionRoot component without props transitions correctly (
Browse files Browse the repository at this point in the history
…#3147)

* ensure `TransitionRoot` component without props transitions correctly

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.

* update changelog
  • Loading branch information
RobinMalfait committed Apr 26, 2024
1 parent 539c124 commit afc9cb6
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add optional `onClose` callback to `Combobox` component ([#3122](https://github.com/tailwindlabs/headlessui/pull/3122))
- Make sure `data-disabled` is available on virtualized options in the `Combobox` component ([#3128](https://github.com/tailwindlabs/headlessui/pull/3128))
- Add `overflow: auto` when using the `anchor` prop ([#3138](https://github.com/tailwindlabs/headlessui/pull/3138))
- Ensure `TransitionRoot` component without props transitions correctly ([#3147](https://github.com/tailwindlabs/headlessui/pull/3147))

### Changed

Expand Down
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 afc9cb6

Please sign in to comment.