Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dull-berries-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': patch
---

Update useMergedRefs so that in React 18 no warning is emitted
10 changes: 10 additions & 0 deletions packages/react/src/hooks/__tests__/useMergedRefs.test.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement & HTMLButtonElement>
Expand Down Expand Up @@ -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()

Expand Down
6 changes: 6 additions & 0 deletions packages/react/src/hooks/useMergedRefs.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -37,6 +38,11 @@ export function useMergedRefs<T>(refA: Ref<T | null>, refB: Ref<T | null>) {
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 () => {
Expand Down
7 changes: 0 additions & 7 deletions packages/react/src/utils/environment.js

This file was deleted.

22 changes: 22 additions & 0 deletions packages/react/src/utils/environment.ts
Original file line number Diff line number Diff line change
@@ -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}
Loading