Skip to content

Commit 0162c57

Browse files
RobinMalfaitsilvenonthecrypticace
committed
add React 18 compatibility (#1326)
* bump dev dependencies to React 18 * setup Jest to include `IS_REACT_ACT_ENVIRONMENT` * prefer `useId` from React 18 if it exists In React 16 & 17, where `useId` doesn't exist, we will fallback to our implementation we have been using up until now. The `useId` exposed by React 18, ensures stable references even in SSR environments. * update expected events React 18 now uses the proper events: - `blur` -> `focusout` - `focus` -> `focusin` * ensure to wait a bit longer This is a bit unfortunate, but since React 18 now does an extra unmount/remount in `StrictMode` to ensure that your code is ConcurrentMode ready, it takes a bit longer to settle what the DOM sees. That said, this is a temporary "hack". We are going to experiment with using tools like Puppeteer/Playwright to run our tests in an actual browser instead to eliminate all the weird details that we have to keep in mind. * prefer `.focus()` over `fireEvent.focus(el)` * abstract `microTask` polyfill code * prefer our `focus(el)` function over `el.focus()` Internally we would still use `el.focus()`, but this allows us to have more control over that `focus` function. * add React 18 to the React Playground * improve hooks for React 18 - Improving the cleanup of useEffect hooks - useIsoMorphicEffect instead of normal useEffect, so that we can use useLayoutEffect to be a bit quicker. * improve disposables - This allows us to add event listeners on a node, and get automatic cleanup once `dispose` gets called. - We also return all the `d.add` calls, so that we can cleanup specific parts only instead of everything or nothing. * reimplement the Transition component to be React 18 ready * wait an additional frame for everything to settle * update playground examples * suppressConsoleLogs for RadioGroup components * update changelog * keep the `to` classes for a smoother transition In the next transition we will remove _all_ classes provided and re-add the once we need. --- Some extra special thanks: - Thanks @silvenon for your initial work on the `transition` events in #926 - Thanks @thecrypticace for doing late-night debugging sessions Co-authored-by: =?UTF-8?q?Matija=20Marohni=C4=87?= <[email protected]> Co-authored-by: Jordan Pittman <[email protected]>
1 parent ab6310c commit 0162c57

34 files changed

+1633
-1314
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
- Mimic browser select on focus when navigating via `Tab` ([#1272](https://github.com/tailwindlabs/headlessui/pull/1272))
3636
- Ensure that there is always an active option in the `Combobox` ([#1279](https://github.com/tailwindlabs/headlessui/pull/1279), [#1281](https://github.com/tailwindlabs/headlessui/pull/1281))
3737
- Allow `Enter` for form submit in `RadioGroup`, `Switch` and `Combobox` improvements ([#1285](https://github.com/tailwindlabs/headlessui/pull/1285))
38+
- add React 18 compatibility ([#1326](https://github.com/tailwindlabs/headlessui/pull/1326))
3839

3940
### Added
4041

jest/create-jest-config.cjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
module.exports = function createJestConfig(root, options) {
2+
let { setupFilesAfterEnv = [], transform = {}, ...rest } = options
23
return Object.assign(
34
{
45
rootDir: root,
5-
setupFilesAfterEnv: ['<rootDir>../../jest/custom-matchers.ts'],
6+
setupFilesAfterEnv: ['<rootDir>../../jest/custom-matchers.ts', ...setupFilesAfterEnv],
67
transform: {
78
'^.+\\.(t|j)sx?$': '@swc/jest',
9+
...transform,
810
},
911
},
10-
options
12+
rest
1113
)
1214
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"devDependencies": {
4040
"@swc/core": "^1.2.131",
4141
"@swc/jest": "^0.2.17",
42-
"@testing-library/jest-dom": "^5.11.9",
42+
"@testing-library/jest-dom": "^5.16.4",
4343
"@types/node": "^14.14.22",
4444
"esbuild": "^0.14.11",
4545
"fast-glob": "^3.2.11",
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
let create = require('../../jest/create-jest-config.cjs')
2-
module.exports = create(__dirname, { displayName: 'React' })
2+
module.exports = create(__dirname, {
3+
displayName: 'React',
4+
setupFilesAfterEnv: ['./jest.setup.js'],
5+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
globalThis.IS_REACT_ACT_ENVIRONMENT = true

packages/@headlessui-react/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@
4242
"react-dom": "^16 || ^17 || ^18"
4343
},
4444
"devDependencies": {
45-
"@testing-library/react": "^11.2.3",
46-
"@types/react": "16.14.21",
47-
"@types/react-dom": "^16.9.0",
45+
"@testing-library/react": "^13.0.0",
46+
"@types/react": "^17.0.43",
47+
"@types/react-dom": "^17.0.14",
4848
"esbuild": "^0.11.18",
49-
"react": "^16.14.0",
50-
"react-dom": "^16.14.0",
49+
"react": "^18.0.0",
50+
"react-dom": "^18.0.0",
5151
"snapshot-diff": "^0.8.1"
5252
}
5353
}

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

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ describe('Keyboard interactions', () => {
866866
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
867867

868868
// Focus the button
869-
getComboboxButton()?.focus()
869+
await focus(getComboboxButton())
870870

871871
// Open combobox
872872
await press(Keys.Enter)
@@ -915,7 +915,7 @@ describe('Keyboard interactions', () => {
915915
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
916916

917917
// Try to focus the button
918-
getComboboxButton()?.focus()
918+
await focus(getComboboxButton())
919919

920920
// Try to open the combobox
921921
await press(Keys.Enter)
@@ -951,7 +951,7 @@ describe('Keyboard interactions', () => {
951951
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
952952

953953
// Focus the button
954-
getComboboxButton()?.focus()
954+
await focus(getComboboxButton())
955955

956956
// Open combobox
957957
await press(Keys.Enter)
@@ -1000,7 +1000,7 @@ describe('Keyboard interactions', () => {
10001000
assertComboboxList({ state: ComboboxState.InvisibleHidden })
10011001

10021002
// Focus the button
1003-
getComboboxButton()?.focus()
1003+
await focus(getComboboxButton())
10041004

10051005
// Open combobox
10061006
await press(Keys.Enter)
@@ -1073,7 +1073,7 @@ describe('Keyboard interactions', () => {
10731073
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
10741074

10751075
// Focus the button
1076-
getComboboxButton()?.focus()
1076+
await focus(getComboboxButton())
10771077

10781078
// Open combobox
10791079
await press(Keys.Enter)
@@ -1114,7 +1114,7 @@ describe('Keyboard interactions', () => {
11141114
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
11151115

11161116
// Focus the button
1117-
getComboboxButton()?.focus()
1117+
await focus(getComboboxButton())
11181118

11191119
// Open combobox
11201120
await press(Keys.Enter)
@@ -1153,7 +1153,7 @@ describe('Keyboard interactions', () => {
11531153
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
11541154

11551155
// Focus the button
1156-
getComboboxButton()?.focus()
1156+
await focus(getComboboxButton())
11571157

11581158
// Open combobox
11591159
await press(Keys.Space)
@@ -1200,7 +1200,7 @@ describe('Keyboard interactions', () => {
12001200
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
12011201

12021202
// Focus the button
1203-
getComboboxButton()?.focus()
1203+
await focus(getComboboxButton())
12041204

12051205
// Try to open the combobox
12061206
await press(Keys.Space)
@@ -1238,7 +1238,7 @@ describe('Keyboard interactions', () => {
12381238
})
12391239

12401240
// Focus the button
1241-
getComboboxButton()?.focus()
1241+
await focus(getComboboxButton())
12421242

12431243
// Open combobox
12441244
await press(Keys.Space)
@@ -1278,7 +1278,7 @@ describe('Keyboard interactions', () => {
12781278
})
12791279

12801280
// Focus the button
1281-
getComboboxButton()?.focus()
1281+
await focus(getComboboxButton())
12821282

12831283
// Open combobox
12841284
await press(Keys.Space)
@@ -1319,7 +1319,7 @@ describe('Keyboard interactions', () => {
13191319
})
13201320

13211321
// Focus the button
1322-
getComboboxButton()?.focus()
1322+
await focus(getComboboxButton())
13231323

13241324
// Open combobox
13251325
await press(Keys.Space)
@@ -1358,7 +1358,7 @@ describe('Keyboard interactions', () => {
13581358
assertComboboxButtonLinkedWithCombobox()
13591359

13601360
// Re-focus the button
1361-
getComboboxButton()?.focus()
1361+
await focus(getComboboxButton())
13621362
assertActiveElement(getComboboxButton())
13631363

13641364
// Close combobox
@@ -1397,7 +1397,7 @@ describe('Keyboard interactions', () => {
13971397
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
13981398

13991399
// Focus the button
1400-
getComboboxButton()?.focus()
1400+
await focus(getComboboxButton())
14011401

14021402
// Open combobox
14031403
await press(Keys.ArrowDown)
@@ -1443,7 +1443,7 @@ describe('Keyboard interactions', () => {
14431443
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
14441444

14451445
// Focus the button
1446-
getComboboxButton()?.focus()
1446+
await focus(getComboboxButton())
14471447

14481448
// Try to open the combobox
14491449
await press(Keys.ArrowDown)
@@ -1479,7 +1479,7 @@ describe('Keyboard interactions', () => {
14791479
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
14801480

14811481
// Focus the button
1482-
getComboboxButton()?.focus()
1482+
await focus(getComboboxButton())
14831483

14841484
// Open combobox
14851485
await press(Keys.ArrowDown)
@@ -1517,7 +1517,7 @@ describe('Keyboard interactions', () => {
15171517
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
15181518

15191519
// Focus the button
1520-
getComboboxButton()?.focus()
1520+
await focus(getComboboxButton())
15211521

15221522
// Open combobox
15231523
await press(Keys.ArrowDown)
@@ -1552,7 +1552,7 @@ describe('Keyboard interactions', () => {
15521552
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
15531553

15541554
// Focus the button
1555-
getComboboxButton()?.focus()
1555+
await focus(getComboboxButton())
15561556

15571557
// Open combobox
15581558
await press(Keys.ArrowUp)
@@ -1598,7 +1598,7 @@ describe('Keyboard interactions', () => {
15981598
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
15991599

16001600
// Focus the button
1601-
getComboboxButton()?.focus()
1601+
await focus(getComboboxButton())
16021602

16031603
// Try to open the combobox
16041604
await press(Keys.ArrowUp)
@@ -1634,7 +1634,7 @@ describe('Keyboard interactions', () => {
16341634
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
16351635

16361636
// Focus the button
1637-
getComboboxButton()?.focus()
1637+
await focus(getComboboxButton())
16381638

16391639
// Open combobox
16401640
await press(Keys.ArrowUp)
@@ -1672,7 +1672,7 @@ describe('Keyboard interactions', () => {
16721672
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
16731673

16741674
// Focus the button
1675-
getComboboxButton()?.focus()
1675+
await focus(getComboboxButton())
16761676

16771677
// Open combobox
16781678
await press(Keys.ArrowUp)
@@ -1709,7 +1709,7 @@ describe('Keyboard interactions', () => {
17091709
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
17101710

17111711
// Focus the button
1712-
getComboboxButton()?.focus()
1712+
await focus(getComboboxButton())
17131713

17141714
// Open combobox
17151715
await press(Keys.ArrowUp)
@@ -1899,7 +1899,7 @@ describe('Keyboard interactions', () => {
18991899
render(<Example />)
19001900

19011901
// Focus the input field
1902-
getComboboxInput()?.focus()
1902+
await focus(getComboboxInput())
19031903
assertActiveElement(getComboboxInput())
19041904

19051905
// Press enter (which should submit the form)
@@ -2212,7 +2212,7 @@ describe('Keyboard interactions', () => {
22122212
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
22132213

22142214
// Focus the input
2215-
getComboboxInput()?.focus()
2215+
await focus(getComboboxInput())
22162216

22172217
// Open combobox
22182218
await press(Keys.ArrowDown)
@@ -2258,7 +2258,7 @@ describe('Keyboard interactions', () => {
22582258
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
22592259

22602260
// Focus the input
2261-
getComboboxInput()?.focus()
2261+
await focus(getComboboxInput())
22622262

22632263
// Try to open the combobox
22642264
await press(Keys.ArrowDown)
@@ -2294,7 +2294,7 @@ describe('Keyboard interactions', () => {
22942294
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
22952295

22962296
// Focus the input
2297-
getComboboxInput()?.focus()
2297+
await focus(getComboboxInput())
22982298

22992299
// Open combobox
23002300
await press(Keys.ArrowDown)
@@ -2332,7 +2332,7 @@ describe('Keyboard interactions', () => {
23322332
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
23332333

23342334
// Focus the input
2335-
getComboboxInput()?.focus()
2335+
await focus(getComboboxInput())
23362336

23372337
// Open combobox
23382338
await press(Keys.ArrowDown)
@@ -2527,7 +2527,7 @@ describe('Keyboard interactions', () => {
25272527
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
25282528

25292529
// Focus the input
2530-
getComboboxInput()?.focus()
2530+
await focus(getComboboxInput())
25312531

25322532
// Open combobox
25332533
await press(Keys.ArrowUp)
@@ -2573,7 +2573,7 @@ describe('Keyboard interactions', () => {
25732573
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
25742574

25752575
// Focus the input
2576-
getComboboxInput()?.focus()
2576+
await focus(getComboboxInput())
25772577

25782578
// Try to open the combobox
25792579
await press(Keys.ArrowUp)
@@ -2609,7 +2609,7 @@ describe('Keyboard interactions', () => {
26092609
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
26102610

26112611
// Focus the input
2612-
getComboboxInput()?.focus()
2612+
await focus(getComboboxInput())
26132613

26142614
// Open combobox
26152615
await press(Keys.ArrowUp)
@@ -2647,7 +2647,7 @@ describe('Keyboard interactions', () => {
26472647
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
26482648

26492649
// Focus the input
2650-
getComboboxInput()?.focus()
2650+
await focus(getComboboxInput())
26512651

26522652
// Open combobox
26532653
await press(Keys.ArrowUp)
@@ -2684,7 +2684,7 @@ describe('Keyboard interactions', () => {
26842684
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
26852685

26862686
// Focus the input
2687-
getComboboxInput()?.focus()
2687+
await focus(getComboboxInput())
26882688

26892689
// Open combobox
26902690
await press(Keys.ArrowUp)
@@ -2766,7 +2766,7 @@ describe('Keyboard interactions', () => {
27662766
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })
27672767

27682768
// Focus the input
2769-
getComboboxInput()?.focus()
2769+
await focus(getComboboxInput())
27702770

27712771
// Open combobox
27722772
await press(Keys.ArrowUp)
@@ -3099,7 +3099,7 @@ describe('Keyboard interactions', () => {
30993099
)
31003100

31013101
// Focus the input
3102-
getComboboxInput()?.focus()
3102+
await focus(getComboboxInput())
31033103

31043104
// Open combobox
31053105
await press(Keys.ArrowUp)
@@ -3243,7 +3243,7 @@ describe('Keyboard interactions', () => {
32433243
)
32443244

32453245
// Focus the input
3246-
getComboboxInput()?.focus()
3246+
await focus(getComboboxInput())
32473247

32483248
// Open combobox
32493249
await press(Keys.ArrowUp)

0 commit comments

Comments
 (0)