Skip to content

Commit f6d25c6

Browse files
mannycarrera4manuel.carreraalanbsmith
authored
fix: Update useUniqueId to use unicode safe selector (#3643)
Fix the issue introduced by React's `useId` function where it added `:` before ids. This creates a conflict with resting libraries because `:` is a valid CSS selector. More information in this issue [here](facebook/react#32001). [category:Components] Co-authored-by: manuel.carrera <[email protected]> Co-authored-by: @alanbsmith <[email protected]>
1 parent 10bf18b commit f6d25c6

File tree

3 files changed

+52
-2
lines changed

3 files changed

+52
-2
lines changed

jest/setupTests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import '@testing-library/jest-dom/extend-expect';
1+
import '@testing-library/jest-dom';
22
import {verifyComponent} from './verifyComponent';
33
import {jest} from '@jest/globals';
44
import {ResizeObserver} from '@juggle/resize-observer';

modules/react/common/lib/utils/useUniqueId.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,20 @@ export const generateUniqueId = () => seed + (c++).toString(36);
2626
/**
2727
* Generate a unique ID if one is not provided. The generated ID will be stable across renders. Uses
2828
* `React.useId()` if available.
29+
*
30+
* Note: In React 18, `useId()` generates IDs with colons (e.g., `:r0:`), which are not valid in CSS
31+
* selectors. We transform to use unicode guillemets (`«r0»`) matching React's upcoming format
32+
* change (https://github.com/facebook/react/pull/32001).
33+
*
2934
* @param id Optional ID provided that will be used instead of a unique ID
3035
*/
3136
export const useUniqueId = (id?: string) => {
3237
// https://codesandbox.io/s/react-functional-component-ids-p2ndq
3338
// eslint-disable-next-line react-hooks/rules-of-hooks
34-
const generatedId = hasStableId ? React.useId() : useConstant(generateUniqueId);
39+
const reactId = hasStableId ? React.useId() : useConstant(generateUniqueId);
40+
// Transform React's useId format (:r0:) to CSS-safe format («r0»)
41+
// This matches React 19's [format](https://github.com/facebook/react/pull/32001). When we bump to >= React 19.1.0, we can remove this logic and use `useId()` directly.
42+
const generatedId = hasStableId ? reactId.replace(/^:/, '«').replace(/:$/, '»') : reactId;
3543
return id || generatedId;
3644
};
3745

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
3+
import {Basic} from '../stories/examples/Basic';
4+
5+
describe('Menu with RTL queries (verifying useId fix)', () => {
6+
const BasicMenu = () => {
7+
return <Basic />;
8+
};
9+
10+
it('should work with getByText and fireEvent.click', async () => {
11+
render(<BasicMenu />);
12+
13+
// Click the menu target using getByText - this would fail with :r0: IDs
14+
const target = screen.getByText('Open Menu');
15+
fireEvent.click(target);
16+
17+
// Find menu item using getByText
18+
const firstItem = await screen.findByText('First Item');
19+
fireEvent.click(firstItem);
20+
21+
// Verify selection worked
22+
await waitFor(() => {
23+
expect(screen.getByTestId('output')).toHaveTextContent('0');
24+
});
25+
});
26+
27+
it('should work with getByRole queries', async () => {
28+
render(<BasicMenu />);
29+
30+
// Use role-based query
31+
const target = screen.getByRole('button', {name: 'Open Menu'});
32+
fireEvent.click(target);
33+
34+
// Menu items have menuitem role
35+
const secondItem = await screen.findByRole('menuitem', {name: 'Second Item'});
36+
fireEvent.click(secondItem);
37+
38+
await waitFor(() => {
39+
expect(screen.getByTestId('output')).toHaveTextContent('1');
40+
});
41+
});
42+
});

0 commit comments

Comments
 (0)