Ensure TransitionRoot component without props transitions correctly (#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
This commit is contained in:
Robin Malfait
2024-04-26 23:28:43 +02:00
committed by GitHub
parent 539c124c69
commit afc9cb648b
3 changed files with 23 additions and 13 deletions
+1
View File
@@ -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
@@ -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)
})
@@ -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])
@@ -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])