diff --git a/.changeset/dull-berries-call.md b/.changeset/dull-berries-call.md new file mode 100644 index 00000000000..ea8c4b5901f --- /dev/null +++ b/.changeset/dull-berries-call.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Update useMergedRefs so that in React 18 no warning is emitted diff --git a/packages/react/src/hooks/__tests__/useMergedRefs.test.tsx b/packages/react/src/hooks/__tests__/useMergedRefs.test.tsx index 329dd9e8908..4e826cf1e32 100644 --- a/packages/react/src/hooks/__tests__/useMergedRefs.test.tsx +++ b/packages/react/src/hooks/__tests__/useMergedRefs.test.tsx @@ -1,6 +1,7 @@ import {render, renderHook} from '@testing-library/react' import React, {forwardRef, type RefObject} from 'react' import {describe, expect, it, vi} from 'vitest' +import {reactMajorVersion} from '../../utils/environment' import {useMergedRefs} from '../useMergedRefs' type InputOrButtonRef = RefObject @@ -142,6 +143,15 @@ describe('useMergedRefs', () => { expect(refA).toHaveBeenCalledWith('test') expect(refB).toHaveBeenCalledWith('test') + if (reactMajorVersion < 19) { + expect(cleanup).toBeUndefined() + return + } + + if (typeof cleanup !== 'function') { + throw new Error('Expected a cleanup function when running on React 19+') + } + // React 19 will call cleanup function and not pass null cleanup() diff --git a/packages/react/src/hooks/useMergedRefs.ts b/packages/react/src/hooks/useMergedRefs.ts index 4b3ed9ab661..00bb7ec307c 100644 --- a/packages/react/src/hooks/useMergedRefs.ts +++ b/packages/react/src/hooks/useMergedRefs.ts @@ -1,5 +1,6 @@ import type {ForwardedRef, Ref as StandardRef, MutableRefObject} from 'react' import {useCallback} from 'react' +import {reactMajorVersion} from '../utils/environment' /** * Combine two refs of matching type (typically an external or forwarded ref and an internal `useRef` object or @@ -37,6 +38,11 @@ export function useMergedRefs(refA: Ref, refB: Ref) { const cleanupA = setRef(refA, value) const cleanupB = setRef(refB, value) + // TODO: remove when we are on React 19 + if (reactMajorVersion < 19) { + return + } + // Only works in React 19. In React 18, the cleanup function will be ignored and the ref will get called with // `null` which will be passed to each ref as expected. return () => { diff --git a/packages/react/src/utils/environment.js b/packages/react/src/utils/environment.js deleted file mode 100644 index 05d95019d8a..00000000000 --- a/packages/react/src/utils/environment.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Indicate whether current execution environment can access the DOM. - * - * @see https://github.com/facebook/fbjs/blob/4d1751311d3f67af2dcce2e40df8512a23c7b9c6/packages/fbjs/src/core/ExecutionEnvironment.js#L12 - */ -// eslint-disable-next-line ssr-friendly/no-dom-globals-in-module-scope -export const canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement) diff --git a/packages/react/src/utils/environment.ts b/packages/react/src/utils/environment.ts new file mode 100644 index 00000000000..4e8db18f46f --- /dev/null +++ b/packages/react/src/utils/environment.ts @@ -0,0 +1,22 @@ +import {version} from 'react' + +/** + * Indicate whether current execution environment can access the DOM. + * + * @see https://github.com/facebook/fbjs/blob/4d1751311d3f67af2dcce2e40df8512a23c7b9c6/packages/fbjs/src/core/ExecutionEnvironment.js#L12 + */ +// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition,ssr-friendly/no-dom-globals-in-module-scope +const canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement) + +// Grab the major version from react. This could be formatted as any valid +// semver version, e.g.: +// +// - 19.0.0 +// - 19.0.0-rc.1 +// - 0.0.0-{channel}-{commit}-{time} +// +// So we only pull the first part of the version and parse it. +const reactVersion = version.split('.') +const reactMajorVersion = parseInt(reactVersion[0], 10) + +export {canUseDOM, reactMajorVersion}