From afc9cb648b1f1925694510073501098266737078 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 26 Apr 2024 23:28:43 +0200 Subject: [PATCH] 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 --- packages/@headlessui-react/CHANGELOG.md | 1 + .../src/components/transition/transition.tsx | 3 +- .../src/hooks/use-transition.ts | 32 ++++++++++++------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index e2ce5f7f3a..81fe59f334 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -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 diff --git a/packages/@headlessui-react/src/components/transition/transition.tsx b/packages/@headlessui-react/src/components/transition/transition.tsx index ea107653a5..bf91efe6eb 100644 --- a/packages/@headlessui-react/src/components/transition/transition.tsx +++ b/packages/@headlessui-react/src/components/transition/transition.tsx @@ -559,6 +559,7 @@ function TransitionRootFn { + if (show) return setState(TreeStates.Hidden) }) @@ -590,7 +591,7 @@ function TransitionRootFn { if (show) { setState(TreeStates.Visible) - } else if (!hasChildren(nestingBag)) { + } else if (!hasChildren(nestingBag) && internalTransitionRef.current !== null) { setState(TreeStates.Hidden) } }, [show, nestingBag]) diff --git a/packages/@headlessui-react/src/hooks/use-transition.ts b/packages/@headlessui-react/src/hooks/use-transition.ts index 2594bf85b1..e8114dd8c4 100644 --- a/packages/@headlessui-react/src/hooks/use-transition.ts +++ b/packages/@headlessui-react/src/hooks/use-transition.ts @@ -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])