Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add React 18 compatibility (#1326)
Browse files Browse the repository at this point in the history
* 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.
RobinMalfait authored Apr 12, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent ab6310c commit fbfc86f
Showing 34 changed files with 1,633 additions and 1,314 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Mimic browser select on focus when navigating via `Tab` ([#1272](https://github.com/tailwindlabs/headlessui/pull/1272))
- 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))
- Allow `Enter` for form submit in `RadioGroup`, `Switch` and `Combobox` improvements ([#1285](https://github.com/tailwindlabs/headlessui/pull/1285))
- add React 18 compatibility ([#1326](https://github.com/tailwindlabs/headlessui/pull/1326))

### Added

6 changes: 4 additions & 2 deletions jest/create-jest-config.cjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
module.exports = function createJestConfig(root, options) {
let { setupFilesAfterEnv = [], transform = {}, ...rest } = options
return Object.assign(
{
rootDir: root,
setupFilesAfterEnv: ['<rootDir>../../jest/custom-matchers.ts'],
setupFilesAfterEnv: ['<rootDir>../../jest/custom-matchers.ts', ...setupFilesAfterEnv],
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
...transform,
},
},
options
rest
)
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@
"devDependencies": {
"@swc/core": "^1.2.131",
"@swc/jest": "^0.2.17",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/jest-dom": "^5.16.4",
"@types/node": "^14.14.22",
"esbuild": "^0.14.11",
"fast-glob": "^3.2.11",
5 changes: 4 additions & 1 deletion packages/@headlessui-react/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
let create = require('../../jest/create-jest-config.cjs')
module.exports = create(__dirname, { displayName: 'React' })
module.exports = create(__dirname, {
displayName: 'React',
setupFilesAfterEnv: ['./jest.setup.js'],
})
1 change: 1 addition & 0 deletions packages/@headlessui-react/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
globalThis.IS_REACT_ACT_ENVIRONMENT = true
10 changes: 5 additions & 5 deletions packages/@headlessui-react/package.json
Original file line number Diff line number Diff line change
@@ -42,12 +42,12 @@
"react-dom": "^16 || ^17 || ^18"
},
"devDependencies": {
"@testing-library/react": "^11.2.3",
"@types/react": "16.14.21",
"@types/react-dom": "^16.9.0",
"@testing-library/react": "^13.0.0",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"esbuild": "^0.11.18",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"snapshot-diff": "^0.8.1"
}
}
Original file line number Diff line number Diff line change
@@ -866,7 +866,7 @@ describe('Keyboard interactions', () => {
assertComboboxList({ state: ComboboxState.InvisibleUnmounted })

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Try to focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Re-focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())
assertActiveElement(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the button
getComboboxButton()?.focus()
await focus(getComboboxButton())

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

// Focus the input field
getComboboxInput()?.focus()
await focus(getComboboxInput())
assertActiveElement(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

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

// Focus the input
getComboboxInput()?.focus()
await focus(getComboboxInput())

// Open combobox
await press(Keys.ArrowUp)
Loading

0 comments on commit fbfc86f

Please sign in to comment.