Skip to content

Commit 8d0fc0a

Browse files
authored
perf: useId logic (#476)
1 parent 9d5cb89 commit 8d0fc0a

File tree

3 files changed

+101
-58
lines changed

3 files changed

+101
-58
lines changed

src/hooks/useId.ts

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,47 @@ export function resetUuid() {
1818
}
1919
}
2020

21-
export default function useId(id?: string) {
22-
// Inner id for accessibility usage. Only work in client side
23-
const [innerId, setInnerId] = React.useState<string>('ssr-id');
21+
const useOriginId = getUseId();
2422

25-
const useOriginId = getUseId();
26-
const reactNativeId = useOriginId?.();
23+
export default useOriginId
24+
? // Use React `useId`
25+
function useId(id?: string) {
26+
const reactId = useOriginId();
2727

28-
React.useEffect(() => {
29-
if (!useOriginId) {
30-
const nextId = uuid;
31-
uuid += 1;
28+
// Developer passed id is single source of truth
29+
if (id) {
30+
return id;
31+
}
3232

33-
setInnerId(`rc_unique_${nextId}`);
33+
// Test env always return mock id
34+
if (process.env.NODE_ENV === 'test') {
35+
return 'test-id';
36+
}
37+
38+
return reactId;
3439
}
35-
}, []);
40+
: // Use compatible of `useId`
41+
function useCompatId(id?: string) {
42+
// Inner id for accessibility usage. Only work in client side
43+
const [innerId, setInnerId] = React.useState<string>('ssr-id');
3644

37-
// Developer passed id is single source of truth
38-
if (id) {
39-
return id;
40-
}
45+
React.useEffect(() => {
46+
const nextId = uuid;
47+
uuid += 1;
4148

42-
// Test env always return mock id
43-
if (process.env.NODE_ENV === 'test') {
44-
return 'test-id';
45-
}
49+
setInnerId(`rc_unique_${nextId}`);
50+
}, []);
4651

47-
// Return react native id or inner id
48-
return reactNativeId || innerId;
49-
}
52+
// Developer passed id is single source of truth
53+
if (id) {
54+
return id;
55+
}
56+
57+
// Test env always return mock id
58+
if (process.env.NODE_ENV === 'test') {
59+
return 'test-id';
60+
}
61+
62+
// Return react native id or inner id
63+
return innerId;
64+
};

tests/hooks-17.test.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { render } from '@testing-library/react';
2+
import * as React from 'react';
3+
import { renderToString } from 'react-dom/server';
4+
import useId, { resetUuid } from '../src/hooks/useId';
5+
6+
jest.mock('react', () => {
7+
const react = jest.requireActual('react');
8+
9+
const clone = { ...react };
10+
11+
Object.defineProperty(clone, 'useId', {
12+
get: () => null,
13+
});
14+
15+
return clone;
16+
});
17+
18+
describe('hooks-17', () => {
19+
describe('useId', () => {
20+
const Demo = ({ id } = {}) => {
21+
const mergedId = useId(id);
22+
return <div id={mergedId} className="target" />;
23+
};
24+
25+
function matchId(container, id) {
26+
const ele = container.querySelector('.target');
27+
return expect(ele.id).toEqual(id);
28+
}
29+
30+
it('fallback of React 17 or lower', () => {
31+
const errorSpy = jest.spyOn(console, 'error');
32+
const originEnv = process.env.NODE_ENV;
33+
process.env.NODE_ENV = 'development';
34+
35+
// SSR
36+
const content = renderToString(
37+
<React.StrictMode>
38+
<Demo />
39+
</React.StrictMode>,
40+
);
41+
expect(content).toContain('ssr-id');
42+
43+
// Hydrate
44+
resetUuid();
45+
const holder = document.createElement('div');
46+
holder.innerHTML = content;
47+
const { container } = render(
48+
<React.StrictMode>
49+
<Demo />
50+
</React.StrictMode>,
51+
{
52+
hydrate: true,
53+
container: holder,
54+
},
55+
);
56+
57+
matchId(container, 'rc_unique_1');
58+
59+
errorSpy.mockRestore();
60+
process.env.NODE_ENV = originEnv;
61+
});
62+
});
63+
});

tests/hooks.test.js

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { fireEvent, render } from '@testing-library/react';
22
import * as React from 'react';
33
import { renderToString } from 'react-dom/server';
4-
import useId, { resetUuid } from '../src/hooks/useId';
4+
import useId from '../src/hooks/useId';
55
import useLayoutEffect from '../src/hooks/useLayoutEffect';
66
import useMemo from '../src/hooks/useMemo';
77
import useMergedState from '../src/hooks/useMergedState';
@@ -487,41 +487,6 @@ describe('hooks', () => {
487487
errorSpy.mockRestore();
488488
process.env.NODE_ENV = originEnv;
489489
});
490-
491-
it('fallback of React 17 or lower', () => {
492-
const errorSpy = jest.spyOn(console, 'error');
493-
const originEnv = process.env.NODE_ENV;
494-
process.env.NODE_ENV = 'development';
495-
global.disableUseId = true;
496-
497-
// SSR
498-
const content = renderToString(
499-
<React.StrictMode>
500-
<Demo />
501-
</React.StrictMode>,
502-
);
503-
expect(content).toContain('ssr-id');
504-
505-
// Hydrate
506-
resetUuid();
507-
const holder = document.createElement('div');
508-
holder.innerHTML = content;
509-
const { container } = render(
510-
<React.StrictMode>
511-
<Demo />
512-
</React.StrictMode>,
513-
{
514-
hydrate: true,
515-
container: holder,
516-
},
517-
);
518-
519-
matchId(container, 'rc_unique_1');
520-
521-
errorSpy.mockRestore();
522-
process.env.NODE_ENV = originEnv;
523-
global.disableUseId = false;
524-
});
525490
});
526491

527492
describe('useMobile', () => {

0 commit comments

Comments
 (0)