diff --git a/packages/react-aria-components/src/Checkbox.tsx b/packages/react-aria-components/src/Checkbox.tsx index 45404c4eba8..fd2298af0d5 100644 --- a/packages/react-aria-components/src/Checkbox.tsx +++ b/packages/react-aria-components/src/Checkbox.tsx @@ -39,7 +39,7 @@ import {HoverEvents} from '@react-types/shared'; import {LabelContext} from './Label'; import {mergeProps} from 'react-aria/mergeProps'; import {mergeRefs} from 'react-aria/mergeRefs'; -import React, {createContext, ForwardedRef, forwardRef, useContext, useMemo} from 'react'; +import React, {createContext, ForwardedRef, forwardRef, Ref, useContext, useMemo} from 'react'; import {TextContext} from './Text'; import {useFocusRing} from 'react-aria/useFocusRing'; import {useHover} from 'react-aria/useHover'; @@ -89,7 +89,7 @@ export interface CheckboxProps /** * A ref for the HTML input element. */ - inputRef?: RefObject; + inputRef?: Ref; } export interface CheckboxFieldProps @@ -109,7 +109,7 @@ export interface CheckboxFieldProps /** * A ref for the HTML input element. */ - inputRef?: RefObject; + inputRef?: Ref; } export interface CheckboxButtonProps @@ -429,7 +429,7 @@ export const CheckboxField = /*#__PURE__*/ (forwardRef as forwardRefType)(functi function useCheckboxAria( props: CheckboxProps | CheckboxFieldProps, - userProvidedInputRef: RefObject | null + userProvidedInputRef: Ref | null ): [CheckboxAria, RefObject] { let {validationBehavior: formValidationBehavior} = useSlottedContext(FormContext) || {}; let validationBehavior = props.validationBehavior ?? formValidationBehavior ?? 'native'; diff --git a/packages/react-aria-components/src/RadioGroup.tsx b/packages/react-aria-components/src/RadioGroup.tsx index 4f9f59dfe8f..caf52c62906 100644 --- a/packages/react-aria-components/src/RadioGroup.tsx +++ b/packages/react-aria-components/src/RadioGroup.tsx @@ -40,7 +40,7 @@ import {LabelContext} from './Label'; import {mergeProps} from 'react-aria/mergeProps'; import {mergeRefs} from 'react-aria/mergeRefs'; import {RadioGroupState, useRadioGroupState} from 'react-stately/useRadioGroupState'; -import React, {createContext, ForwardedRef, forwardRef, useContext, useMemo} from 'react'; +import React, {createContext, ForwardedRef, forwardRef, Ref, useContext, useMemo} from 'react'; import {SelectionIndicatorContext} from './SelectionIndicator'; import {SharedElementTransition} from './SharedElementTransition'; import {TextContext} from './Text'; @@ -89,7 +89,7 @@ export interface RadioProps /** * A ref for the HTML input element. */ - inputRef?: RefObject; + inputRef?: Ref; } export interface RadioFieldProps @@ -108,7 +108,7 @@ export interface RadioFieldProps /** * A ref for the HTML input element. */ - inputRef?: RefObject; + inputRef?: Ref; } export interface RadioButtonProps diff --git a/packages/react-aria-components/src/Switch.tsx b/packages/react-aria-components/src/Switch.tsx index b81cd69c0b3..067458b5858 100644 --- a/packages/react-aria-components/src/Switch.tsx +++ b/packages/react-aria-components/src/Switch.tsx @@ -32,7 +32,7 @@ import {forwardRefType, GlobalDOMAttributes, RefObject} from '@react-types/share import {HoverEvents} from '@react-types/shared'; import {mergeProps} from 'react-aria/mergeProps'; import {mergeRefs} from 'react-aria/mergeRefs'; -import React, {createContext, ForwardedRef, forwardRef, useContext} from 'react'; +import React, {createContext, ForwardedRef, forwardRef, Ref, useContext, useMemo} from 'react'; import {TextContext} from './Text'; import {ToggleState, useToggleState} from 'react-stately/useToggleState'; import {useFocusRing} from 'react-aria/useFocusRing'; @@ -65,7 +65,7 @@ export interface SwitchProps /** * A ref for the HTML input element. */ - inputRef?: RefObject; + inputRef?: Ref; } export interface SwitchFieldProps @@ -85,7 +85,7 @@ export interface SwitchFieldProps /** * A ref for the HTML input element. */ - inputRef?: RefObject; + inputRef?: Ref; } export interface SwitchButtonProps @@ -225,7 +225,10 @@ export const Switch = /*#__PURE__*/ (forwardRef as forwardRefType)(function Swit let {inputRef: userProvidedInputRef = null, ...otherProps} = props; [props, ref] = useContextProps(otherProps, ref, SwitchContext); let inputRef = useObjectRef( - mergeRefs(userProvidedInputRef, props.inputRef !== undefined ? props.inputRef : null) + useMemo( + () => mergeRefs(userProvidedInputRef, props.inputRef !== undefined ? props.inputRef : null), + [userProvidedInputRef, props.inputRef] + ) ); let state = useToggleState(props); let aria = useSwitch( @@ -276,7 +279,10 @@ export const SwitchField = /*#__PURE__*/ (forwardRef as forwardRefType)(function let {validationBehavior: formValidationBehavior} = useSlottedContext(FormContext) || {}; let validationBehavior = props.validationBehavior ?? formValidationBehavior ?? 'native'; let inputRef = useObjectRef( - mergeRefs(userProvidedInputRef, props.inputRef !== undefined ? props.inputRef : null) + useMemo( + () => mergeRefs(userProvidedInputRef, props.inputRef !== undefined ? props.inputRef : null), + [userProvidedInputRef, props.inputRef] + ) ); let state = useToggleState(props); let aria = useSwitch( diff --git a/packages/react-aria-components/test/Checkbox.test.js b/packages/react-aria-components/test/Checkbox.test.js index f87ce806420..7a44370b3e5 100644 --- a/packages/react-aria-components/test/Checkbox.test.js +++ b/packages/react-aria-components/test/Checkbox.test.js @@ -423,6 +423,15 @@ describe.each(['Checkbox', 'CheckboxField'])('%s', comp => { expect(inputRef.current).toBe(getByRole('checkbox')); }); + it('should support callback ref', () => { + let cleanup = jest.fn(); + let onRef = jest.fn(() => cleanup); + let {getByRole, unmount} = render(Test); + expect(onRef).toHaveBeenCalledWith(getByRole('checkbox')); + unmount(); + expect(cleanup).toHaveBeenCalledTimes(1); + }); + it('should support and merge input ref on context', () => { let inputRef = React.createRef(); let contextInputRef = React.createRef(); diff --git a/packages/react-aria-components/test/RadioGroup.test.js b/packages/react-aria-components/test/RadioGroup.test.js index 596497a88a3..2a95a682654 100644 --- a/packages/react-aria-components/test/RadioGroup.test.js +++ b/packages/react-aria-components/test/RadioGroup.test.js @@ -755,6 +755,23 @@ describe.each(['RadioGroup', 'RadioField'])('%s', comp => { expect(inputRef.current).toBe(radio); }); + it('should support callback ref', () => { + let cleanup = jest.fn(); + let onRef = jest.fn(() => cleanup); + let {getByRole, unmount} = render( + + + + A + + + ); + let radio = getByRole('radio'); + expect(onRef).toHaveBeenCalledWith(radio); + unmount(); + expect(cleanup).toHaveBeenCalledTimes(1); + }); + it('should support and merge input ref on context', () => { let inputRef = React.createRef(); let contextInputRef = React.createRef(); diff --git a/packages/react-aria-components/test/Switch.test.js b/packages/react-aria-components/test/Switch.test.js index 5433f99d85d..9f7d6d7f1ae 100644 --- a/packages/react-aria-components/test/Switch.test.js +++ b/packages/react-aria-components/test/Switch.test.js @@ -342,6 +342,15 @@ describe.each(['Switch', 'SwitchField'])('%s', comp => { expect(inputRef.current).toBe(getByRole('switch')); }); + it('should support callback ref', () => { + let cleanup = jest.fn(); + let onRef = jest.fn(() => cleanup); + let {getByRole, unmount} = render(Test); + expect(onRef).toHaveBeenCalledWith(getByRole('switch')); + unmount(); + expect(cleanup).toHaveBeenCalledTimes(1); + }); + it('should support and merge input ref on context', () => { let inputRef = React.createRef(); let contextInputRef = React.createRef();