diff --git a/src/hooks/useId.ts b/src/hooks/useId.ts index 6c34ec49..65337a68 100644 --- a/src/hooks/useId.ts +++ b/src/hooks/useId.ts @@ -18,32 +18,47 @@ export function resetUuid() { } } -export default function useId(id?: string) { - // Inner id for accessibility usage. Only work in client side - const [innerId, setInnerId] = React.useState('ssr-id'); +const useOriginId = getUseId(); - const useOriginId = getUseId(); - const reactNativeId = useOriginId?.(); +export default useOriginId + ? // Use React `useId` + function useId(id?: string) { + const reactId = useOriginId(); - React.useEffect(() => { - if (!useOriginId) { - const nextId = uuid; - uuid += 1; + // Developer passed id is single source of truth + if (id) { + return id; + } - setInnerId(`rc_unique_${nextId}`); + // Test env always return mock id + if (process.env.NODE_ENV === 'test') { + return 'test-id'; + } + + return reactId; } - }, []); + : // Use compatible of `useId` + function useCompatId(id?: string) { + // Inner id for accessibility usage. Only work in client side + const [innerId, setInnerId] = React.useState('ssr-id'); - // Developer passed id is single source of truth - if (id) { - return id; - } + React.useEffect(() => { + const nextId = uuid; + uuid += 1; - // Test env always return mock id - if (process.env.NODE_ENV === 'test') { - return 'test-id'; - } + setInnerId(`rc_unique_${nextId}`); + }, []); - // Return react native id or inner id - return reactNativeId || innerId; -} + // Developer passed id is single source of truth + if (id) { + return id; + } + + // Test env always return mock id + if (process.env.NODE_ENV === 'test') { + return 'test-id'; + } + + // Return react native id or inner id + return innerId; + }; diff --git a/tests/hooks-17.test.js b/tests/hooks-17.test.js new file mode 100644 index 00000000..4ede72b9 --- /dev/null +++ b/tests/hooks-17.test.js @@ -0,0 +1,63 @@ +import { render } from '@testing-library/react'; +import * as React from 'react'; +import { renderToString } from 'react-dom/server'; +import useId, { resetUuid } from '../src/hooks/useId'; + +jest.mock('react', () => { + const react = jest.requireActual('react'); + + const clone = { ...react }; + + Object.defineProperty(clone, 'useId', { + get: () => null, + }); + + return clone; +}); + +describe('hooks-17', () => { + describe('useId', () => { + const Demo = ({ id } = {}) => { + const mergedId = useId(id); + return
; + }; + + function matchId(container, id) { + const ele = container.querySelector('.target'); + return expect(ele.id).toEqual(id); + } + + it('fallback of React 17 or lower', () => { + const errorSpy = jest.spyOn(console, 'error'); + const originEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'development'; + + // SSR + const content = renderToString( + + + , + ); + expect(content).toContain('ssr-id'); + + // Hydrate + resetUuid(); + const holder = document.createElement('div'); + holder.innerHTML = content; + const { container } = render( + + + , + { + hydrate: true, + container: holder, + }, + ); + + matchId(container, 'rc_unique_1'); + + errorSpy.mockRestore(); + process.env.NODE_ENV = originEnv; + }); + }); +}); diff --git a/tests/hooks.test.js b/tests/hooks.test.js index 744a450b..6727ab8b 100644 --- a/tests/hooks.test.js +++ b/tests/hooks.test.js @@ -1,7 +1,7 @@ import { fireEvent, render } from '@testing-library/react'; import * as React from 'react'; import { renderToString } from 'react-dom/server'; -import useId, { resetUuid } from '../src/hooks/useId'; +import useId from '../src/hooks/useId'; import useLayoutEffect from '../src/hooks/useLayoutEffect'; import useMemo from '../src/hooks/useMemo'; import useMergedState from '../src/hooks/useMergedState'; @@ -487,41 +487,6 @@ describe('hooks', () => { errorSpy.mockRestore(); process.env.NODE_ENV = originEnv; }); - - it('fallback of React 17 or lower', () => { - const errorSpy = jest.spyOn(console, 'error'); - const originEnv = process.env.NODE_ENV; - process.env.NODE_ENV = 'development'; - global.disableUseId = true; - - // SSR - const content = renderToString( - - - , - ); - expect(content).toContain('ssr-id'); - - // Hydrate - resetUuid(); - const holder = document.createElement('div'); - holder.innerHTML = content; - const { container } = render( - - - , - { - hydrate: true, - container: holder, - }, - ); - - matchId(container, 'rc_unique_1'); - - errorSpy.mockRestore(); - process.env.NODE_ENV = originEnv; - global.disableUseId = false; - }); }); describe('useMobile', () => {