Skip to content

Commit 551261a

Browse files
authored
Fix "This Suspense boundary received an update before it finished hydrating" error in the Disclosure component (#2238)
* use `startTransition` to work around `Suspense` boundary crash * update changelog
1 parent 2f99644 commit 551261a

File tree

4 files changed

+26
-2
lines changed

4 files changed

+26
-2
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Fix SSR tab hydration when using Strict Mode in development ([#2231](https://github.com/tailwindlabs/headlessui/pull/2231))
1313
- Don't break overflow when multiple dialogs are open at the same time ([#2215](https://github.com/tailwindlabs/headlessui/pull/2215))
14+
- Fix "This `Suspense` boundary received an update before it finished hydrating" error in the `Disclosure` component ([#2238](https://github.com/tailwindlabs/headlessui/pull/2238))
1415

1516
## [1.7.8] - 2023-01-27
1617

packages/@headlessui-react/src/components/disclosure/disclosure.test.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { createElement, useEffect, useRef } from 'react'
1+
import React, { createElement, Suspense, useEffect, useRef } from 'react'
22
import { render } from '@testing-library/react'
33

44
import { Disclosure } from './disclosure'
@@ -236,6 +236,19 @@ describe('Rendering', () => {
236236
assertActiveElement(getByText('restoreable'))
237237
})
238238
)
239+
240+
it('should not crash when using Suspense boundaries', async () => {
241+
render(
242+
<Disclosure defaultOpen={true}>
243+
<Disclosure.Button>Click me!</Disclosure.Button>
244+
<Disclosure.Panel>
245+
<Suspense fallback={null}>
246+
<p>Hi there</p>
247+
</Suspense>
248+
</Disclosure.Panel>
249+
</Disclosure>
250+
)
251+
})
239252
})
240253

241254
describe('Disclosure.Button', () => {

packages/@headlessui-react/src/components/disclosure/disclosure.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-cl
2929
import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
3030
import { getOwnerDocument } from '../../utils/owner'
3131
import { useEvent } from '../../hooks/use-event'
32+
import { startTransition } from '../../utils/start-transition'
3233

3334
enum DisclosureStates {
3435
Open,
@@ -361,7 +362,7 @@ let Panel = forwardRefWithAs(function Panel<TTag extends ElementType = typeof DE
361362
let { close } = useDisclosureAPIContext('Disclosure.Panel')
362363

363364
let panelRef = useSyncRefs(ref, state.panelRef, (el) => {
364-
dispatch({ type: el ? ActionTypes.LinkPanel : ActionTypes.UnlinkPanel })
365+
startTransition(() => dispatch({ type: el ? ActionTypes.LinkPanel : ActionTypes.UnlinkPanel }))
365366
})
366367

367368
useEffect(() => {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react'
2+
3+
export let startTransition =
4+
// Prefer React's `startTransition` if it's available.
5+
// @ts-expect-error - `startTransition` doesn't exist in React < 18.
6+
React.startTransition ??
7+
function startTransition(cb: () => void) {
8+
cb()
9+
}

0 commit comments

Comments
 (0)