From 0d26c0b9b96d8b78772079e6d81bf110e3661c46 Mon Sep 17 00:00:00 2001 From: sai chand <60743144+sai6855@users.noreply.github.com> Date: Thu, 18 Jul 2024 21:29:00 +0530 Subject: [PATCH 1/4] [material-ui] Fix Accessing element.ref (#42818) --- packages/mui-material/src/Fade/Fade.js | 3 ++- packages/mui-material/src/Grow/Grow.js | 3 ++- packages/mui-material/src/Slide/Slide.js | 3 ++- packages/mui-material/src/Tooltip/Tooltip.js | 3 ++- packages/mui-material/src/Zoom/Zoom.js | 3 ++- packages/mui-material/src/utils/getChildRef.d.ts | 1 + packages/mui-material/src/utils/getChildRef.js | 10 ++++++++++ 7 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 packages/mui-material/src/utils/getChildRef.d.ts create mode 100644 packages/mui-material/src/utils/getChildRef.js diff --git a/packages/mui-material/src/Fade/Fade.js b/packages/mui-material/src/Fade/Fade.js index 0258a3adae8723..3996c5f885ae75 100644 --- a/packages/mui-material/src/Fade/Fade.js +++ b/packages/mui-material/src/Fade/Fade.js @@ -6,6 +6,7 @@ import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; import useTheme from '../styles/useTheme'; import { reflow, getTransitionProps } from '../transitions/utils'; import useForkRef from '../utils/useForkRef'; +import getChildRef from '../utils/getChildRef'; const styles = { entering: { @@ -48,7 +49,7 @@ const Fade = React.forwardRef(function Fade(props, ref) { const enableStrictModeCompat = true; const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, children.ref, ref); + const handleRef = useForkRef(nodeRef, getChildRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Grow/Grow.js b/packages/mui-material/src/Grow/Grow.js index 77866f1f107765..45e433d8601f33 100644 --- a/packages/mui-material/src/Grow/Grow.js +++ b/packages/mui-material/src/Grow/Grow.js @@ -7,6 +7,7 @@ import { Transition } from 'react-transition-group'; import useTheme from '../styles/useTheme'; import { getTransitionProps, reflow } from '../transitions/utils'; import useForkRef from '../utils/useForkRef'; +import getChildRef from '../utils/getChildRef'; function getScale(value) { return `scale(${value}, ${value ** 2})`; @@ -61,7 +62,7 @@ const Grow = React.forwardRef(function Grow(props, ref) { const theme = useTheme(); const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, children.ref, ref); + const handleRef = useForkRef(nodeRef, getChildRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Slide/Slide.js b/packages/mui-material/src/Slide/Slide.js index f2d30a7a97d588..12a3b8ac3fbdc2 100644 --- a/packages/mui-material/src/Slide/Slide.js +++ b/packages/mui-material/src/Slide/Slide.js @@ -10,6 +10,7 @@ import useForkRef from '../utils/useForkRef'; import useTheme from '../styles/useTheme'; import { reflow, getTransitionProps } from '../transitions/utils'; import { ownerWindow } from '../utils'; +import getChildRef from '../utils/getChildRef'; // Translate the node so it can't be seen on the screen. // Later, we're going to translate the node back to its original location with `none`. @@ -119,7 +120,7 @@ const Slide = React.forwardRef(function Slide(props, ref) { } = props; const childrenRef = React.useRef(null); - const handleRef = useForkRef(children.ref, childrenRef, ref); + const handleRef = useForkRef(getChildRef(children), childrenRef, ref); const normalizedTransitionCallback = (callback) => (isAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Tooltip/Tooltip.js b/packages/mui-material/src/Tooltip/Tooltip.js index a0dd1879ffa26c..115c252aee7c88 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.js +++ b/packages/mui-material/src/Tooltip/Tooltip.js @@ -19,6 +19,7 @@ import useId from '../utils/useId'; import useIsFocusVisible from '../utils/useIsFocusVisible'; import useControlled from '../utils/useControlled'; import tooltipClasses, { getTooltipUtilityClass } from './tooltipClasses'; +import getChildRef from '../utils/getChildRef'; function round(value) { return Math.round(value * 1e5) / 1e5; @@ -485,7 +486,7 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { }; }, [handleClose, open]); - const handleRef = useForkRef(children.ref, focusVisibleRef, setChildNode, ref); + const handleRef = useForkRef(getChildRef(children), focusVisibleRef, setChildNode, ref); // There is no point in displaying an empty tooltip. // So we exclude all falsy values, except 0, which is valid. diff --git a/packages/mui-material/src/Zoom/Zoom.js b/packages/mui-material/src/Zoom/Zoom.js index 5f0ebfd5d9f781..945357ac828cc2 100644 --- a/packages/mui-material/src/Zoom/Zoom.js +++ b/packages/mui-material/src/Zoom/Zoom.js @@ -6,6 +6,7 @@ import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; import useTheme from '../styles/useTheme'; import { reflow, getTransitionProps } from '../transitions/utils'; import useForkRef from '../utils/useForkRef'; +import getChildRef from '../utils/getChildRef'; const styles = { entering: { @@ -48,7 +49,7 @@ const Zoom = React.forwardRef(function Zoom(props, ref) { } = props; const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, children.ref, ref); + const handleRef = useForkRef(nodeRef, getChildRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-material/src/utils/getChildRef.d.ts b/packages/mui-material/src/utils/getChildRef.d.ts new file mode 100644 index 00000000000000..c7e9c058d3e031 --- /dev/null +++ b/packages/mui-material/src/utils/getChildRef.d.ts @@ -0,0 +1 @@ +export default function getChildRef(child: React.ReactElement): React.Ref | null; diff --git a/packages/mui-material/src/utils/getChildRef.js b/packages/mui-material/src/utils/getChildRef.js new file mode 100644 index 00000000000000..a1d0c2aec5af0c --- /dev/null +++ b/packages/mui-material/src/utils/getChildRef.js @@ -0,0 +1,10 @@ +import * as React from 'react'; + +export default function getChildRef(child) { + if (!child || !React.isValidElement(child)) { + return null; + } + // 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in React 18 + // below check is to ensure 'ref' is accessible in both cases + return child.props.propertyIsEnumerable('ref') ? child.props.ref : child.ref; +} From 474fe587985dfea751c02714e5804d3ff1b001a8 Mon Sep 17 00:00:00 2001 From: Diego Andai Date: Thu, 1 Aug 2024 09:30:22 -0400 Subject: [PATCH 2/4] [material-ui] Element ref access React 19 compatibility (#43132) --- .../ClickAwayListener/ClickAwayListener.tsx | 7 ++---- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 4 ++-- packages/mui-base/src/Portal/Portal.tsx | 4 ++-- packages/mui-joy/src/Tooltip/Tooltip.tsx | 6 ++--- .../ClickAwayListener/ClickAwayListener.tsx | 7 ++---- packages/mui-material/src/Fade/Fade.js | 4 ++-- packages/mui-material/src/Grow/Grow.js | 4 ++-- packages/mui-material/src/Portal/Portal.tsx | 4 ++-- packages/mui-material/src/Select/Select.js | 3 ++- packages/mui-material/src/Slide/Slide.js | 4 ++-- packages/mui-material/src/Tooltip/Tooltip.js | 4 ++-- .../src/Unstable_TrapFocus/FocusTrap.tsx | 4 ++-- packages/mui-material/src/Zoom/Zoom.js | 4 ++-- .../mui-material/src/utils/getChildRef.d.ts | 1 - .../mui-material/src/utils/getChildRef.js | 10 --------- .../src/getReactNodeRef/getReactNodeRef.ts | 22 +++++++++++++++++++ .../mui-utils/src/getReactNodeRef/index.ts | 1 + packages/mui-utils/src/index.ts | 1 + .../src/useForkRef/useForkRef.test.js | 3 ++- 19 files changed, 52 insertions(+), 45 deletions(-) delete mode 100644 packages/mui-material/src/utils/getChildRef.d.ts delete mode 100644 packages/mui-material/src/utils/getChildRef.js create mode 100644 packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts create mode 100644 packages/mui-utils/src/getReactNodeRef/index.ts diff --git a/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx b/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx index ee00d7d9fc8d2b..a7c7c49148d2d5 100644 --- a/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx +++ b/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx @@ -7,6 +7,7 @@ import { unstable_ownerDocument as ownerDocument, unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, + unstable_getReactNodeRef as getReactNodeRef, } from '@mui/utils'; // TODO: return `EventHandlerName extends `on${infer EventName}` ? Lowercase : never` once generatePropTypes runs with TS 4.1 @@ -94,11 +95,7 @@ function ClickAwayListener(props: ClickAwayListenerProps): React.JSX.Element { }; }, []); - const handleRef = useForkRef( - // @ts-expect-error TODO upstream fix - children.ref, - nodeRef, - ); + const handleRef = useForkRef(getReactNodeRef(children), nodeRef); // The handler doesn't take event.defaultPrevented into account: // diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index 3d00b5fc6f7bdc..d17c562746eeed 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -7,6 +7,7 @@ import { elementAcceptingRef, unstable_useForkRef as useForkRef, unstable_ownerDocument as ownerDocument, + unstable_getReactNodeRef as getReactNodeRef, } from '@mui/utils'; import { FocusTrapProps } from './FocusTrap.types'; @@ -152,8 +153,7 @@ function FocusTrap(props: FocusTrapProps): React.JSX.Element { const activated = React.useRef(false); const rootRef = React.useRef(null); - // @ts-expect-error TODO upstream fix - const handleRef = useForkRef(children.ref, rootRef); + const handleRef = useForkRef(getReactNodeRef(children), rootRef); const lastKeydown = React.useRef(null); React.useEffect(() => { diff --git a/packages/mui-base/src/Portal/Portal.tsx b/packages/mui-base/src/Portal/Portal.tsx index c7b5c403decba3..89588fa288ce7d 100644 --- a/packages/mui-base/src/Portal/Portal.tsx +++ b/packages/mui-base/src/Portal/Portal.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; +import getReactNodeRef from '@mui/utils/getReactNodeRef'; import { exactProp, HTMLElementType, @@ -33,8 +34,7 @@ const Portal = React.forwardRef(function Portal( ) { const { children, container, disablePortal = false } = props; const [mountNode, setMountNode] = React.useState>(null); - // @ts-expect-error TODO upstream fix - const handleRef = useForkRef(React.isValidElement(children) ? children.ref : null, forwardedRef); + const handleRef = useForkRef(getReactNodeRef(children), forwardedRef); useEnhancedEffect(() => { if (!disablePortal) { diff --git a/packages/mui-joy/src/Tooltip/Tooltip.tsx b/packages/mui-joy/src/Tooltip/Tooltip.tsx index 531d3a8b288c1b..dd46d08d611e6b 100644 --- a/packages/mui-joy/src/Tooltip/Tooltip.tsx +++ b/packages/mui-joy/src/Tooltip/Tooltip.tsx @@ -11,6 +11,7 @@ import { unstable_useId as useId, unstable_useTimeout as useTimeout, unstable_Timeout as Timeout, + unstable_getReactNodeRef as getReactNodeRef, } from '@mui/utils'; import { Popper, unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; @@ -424,10 +425,7 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { const handleUseRef = useForkRef(setChildNode, ref); const handleFocusRef = useForkRef(focusVisibleRef, handleUseRef); - const handleRef = useForkRef( - (children as unknown as { ref: React.Ref }).ref, - handleFocusRef, - ); + const handleRef = useForkRef(getReactNodeRef(children), handleFocusRef); // There is no point in displaying an empty tooltip. if (typeof title !== 'number' && !title) { diff --git a/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx b/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx index 65333a0180f1e3..7cc39da363978d 100644 --- a/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx +++ b/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx @@ -8,6 +8,7 @@ import { unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, } from '@mui/utils'; +import getReactNodeRef from '@mui/utils/getReactNodeRef'; // TODO: return `EventHandlerName extends `on${infer EventName}` ? Lowercase : never` once generatePropTypes runs with TS 4.1 function mapEventPropToEvent( @@ -95,11 +96,7 @@ function ClickAwayListener(props: ClickAwayListenerProps): JSX.Element { }; }, []); - const handleRef = useForkRef( - // @ts-expect-error TODO upstream fix - children.ref, - nodeRef, - ); + const handleRef = useForkRef(getReactNodeRef(children), nodeRef); // The handler doesn't take event.defaultPrevented into account: // diff --git a/packages/mui-material/src/Fade/Fade.js b/packages/mui-material/src/Fade/Fade.js index 3996c5f885ae75..d4ae9e144b7f7e 100644 --- a/packages/mui-material/src/Fade/Fade.js +++ b/packages/mui-material/src/Fade/Fade.js @@ -3,10 +3,10 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { Transition } from 'react-transition-group'; import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; +import getReactNodeRef from '@mui/utils/getReactNodeRef'; import useTheme from '../styles/useTheme'; import { reflow, getTransitionProps } from '../transitions/utils'; import useForkRef from '../utils/useForkRef'; -import getChildRef from '../utils/getChildRef'; const styles = { entering: { @@ -49,7 +49,7 @@ const Fade = React.forwardRef(function Fade(props, ref) { const enableStrictModeCompat = true; const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, getChildRef(children), ref); + const handleRef = useForkRef(nodeRef, getReactNodeRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Grow/Grow.js b/packages/mui-material/src/Grow/Grow.js index 45e433d8601f33..7df39963d619ef 100644 --- a/packages/mui-material/src/Grow/Grow.js +++ b/packages/mui-material/src/Grow/Grow.js @@ -3,11 +3,11 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import useTimeout from '@mui/utils/useTimeout'; import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; +import getReactNodeRef from '@mui/utils/getReactNodeRef'; import { Transition } from 'react-transition-group'; import useTheme from '../styles/useTheme'; import { getTransitionProps, reflow } from '../transitions/utils'; import useForkRef from '../utils/useForkRef'; -import getChildRef from '../utils/getChildRef'; function getScale(value) { return `scale(${value}, ${value ** 2})`; @@ -62,7 +62,7 @@ const Grow = React.forwardRef(function Grow(props, ref) { const theme = useTheme(); const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, getChildRef(children), ref); + const handleRef = useForkRef(nodeRef, getReactNodeRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Portal/Portal.tsx b/packages/mui-material/src/Portal/Portal.tsx index e2eab377a2d40c..b6aff42416c348 100644 --- a/packages/mui-material/src/Portal/Portal.tsx +++ b/packages/mui-material/src/Portal/Portal.tsx @@ -8,6 +8,7 @@ import { unstable_useEnhancedEffect as useEnhancedEffect, unstable_useForkRef as useForkRef, unstable_setRef as setRef, + unstable_getReactNodeRef as getReactNodeRef, } from '@mui/utils'; import { PortalProps } from './Portal.types'; @@ -33,8 +34,7 @@ const Portal = React.forwardRef(function Portal( ) { const { children, container, disablePortal = false } = props; const [mountNode, setMountNode] = React.useState>(null); - // @ts-expect-error TODO upstream fix - const handleRef = useForkRef(React.isValidElement(children) ? children.ref : null, forwardedRef); + const handleRef = useForkRef(getReactNodeRef(children), forwardedRef); useEnhancedEffect(() => { if (!disablePortal) { diff --git a/packages/mui-material/src/Select/Select.js b/packages/mui-material/src/Select/Select.js index 7c8c7d0836bf73..43717c3fcc7809 100644 --- a/packages/mui-material/src/Select/Select.js +++ b/packages/mui-material/src/Select/Select.js @@ -3,6 +3,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import deepmerge from '@mui/utils/deepmerge'; +import getReactNodeRef from '@mui/utils/getReactNodeRef'; import SelectInput from './SelectInput'; import formControlState from '../FormControl/formControlState'; import useFormControl from '../FormControl/useFormControl'; @@ -84,7 +85,7 @@ const Select = React.forwardRef(function Select(inProps, ref) { filled: , }[variant]; - const inputComponentRef = useForkRef(ref, InputComponent.ref); + const inputComponentRef = useForkRef(ref, getReactNodeRef(InputComponent)); return ( diff --git a/packages/mui-material/src/Slide/Slide.js b/packages/mui-material/src/Slide/Slide.js index 12a3b8ac3fbdc2..452b2d84797a53 100644 --- a/packages/mui-material/src/Slide/Slide.js +++ b/packages/mui-material/src/Slide/Slide.js @@ -5,12 +5,12 @@ import { Transition } from 'react-transition-group'; import chainPropTypes from '@mui/utils/chainPropTypes'; import HTMLElementType from '@mui/utils/HTMLElementType'; import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; +import getReactNodeRef from '@mui/utils/getReactNodeRef'; import debounce from '../utils/debounce'; import useForkRef from '../utils/useForkRef'; import useTheme from '../styles/useTheme'; import { reflow, getTransitionProps } from '../transitions/utils'; import { ownerWindow } from '../utils'; -import getChildRef from '../utils/getChildRef'; // Translate the node so it can't be seen on the screen. // Later, we're going to translate the node back to its original location with `none`. @@ -120,7 +120,7 @@ const Slide = React.forwardRef(function Slide(props, ref) { } = props; const childrenRef = React.useRef(null); - const handleRef = useForkRef(getChildRef(children), childrenRef, ref); + const handleRef = useForkRef(getReactNodeRef(children), childrenRef, ref); const normalizedTransitionCallback = (callback) => (isAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Tooltip/Tooltip.js b/packages/mui-material/src/Tooltip/Tooltip.js index 115c252aee7c88..ffe278f21ea279 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.js +++ b/packages/mui-material/src/Tooltip/Tooltip.js @@ -8,6 +8,7 @@ import composeClasses from '@mui/utils/composeClasses'; import { alpha } from '@mui/system/colorManipulator'; import { useRtl } from '@mui/system/RtlProvider'; import appendOwnerState from '@mui/utils/appendOwnerState'; +import getReactNodeRef from '@mui/utils/getReactNodeRef'; import { styled, useTheme } from '../styles'; import { useDefaultProps } from '../DefaultPropsProvider'; import capitalize from '../utils/capitalize'; @@ -19,7 +20,6 @@ import useId from '../utils/useId'; import useIsFocusVisible from '../utils/useIsFocusVisible'; import useControlled from '../utils/useControlled'; import tooltipClasses, { getTooltipUtilityClass } from './tooltipClasses'; -import getChildRef from '../utils/getChildRef'; function round(value) { return Math.round(value * 1e5) / 1e5; @@ -486,7 +486,7 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { }; }, [handleClose, open]); - const handleRef = useForkRef(getChildRef(children), focusVisibleRef, setChildNode, ref); + const handleRef = useForkRef(getReactNodeRef(children), focusVisibleRef, setChildNode, ref); // There is no point in displaying an empty tooltip. // So we exclude all falsy values, except 0, which is valid. diff --git a/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx b/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx index c936f55b0bf006..a76feff24b020b 100644 --- a/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx +++ b/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx @@ -7,6 +7,7 @@ import { elementAcceptingRef, unstable_useForkRef as useForkRef, unstable_ownerDocument as ownerDocument, + unstable_getReactNodeRef as getReactNodeRef, } from '@mui/utils'; import { FocusTrapProps } from './FocusTrap.types'; @@ -144,8 +145,7 @@ function FocusTrap(props: FocusTrapProps): JSX.Element { const activated = React.useRef(false); const rootRef = React.useRef(null); - // @ts-expect-error TODO upstream fix - const handleRef = useForkRef(children.ref, rootRef); + const handleRef = useForkRef(getReactNodeRef(children), rootRef); const lastKeydown = React.useRef(null); React.useEffect(() => { diff --git a/packages/mui-material/src/Zoom/Zoom.js b/packages/mui-material/src/Zoom/Zoom.js index 945357ac828cc2..ad28f14b3caf6b 100644 --- a/packages/mui-material/src/Zoom/Zoom.js +++ b/packages/mui-material/src/Zoom/Zoom.js @@ -3,10 +3,10 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { Transition } from 'react-transition-group'; import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; +import getReactNodeRef from '@mui/utils/getReactNodeRef'; import useTheme from '../styles/useTheme'; import { reflow, getTransitionProps } from '../transitions/utils'; import useForkRef from '../utils/useForkRef'; -import getChildRef from '../utils/getChildRef'; const styles = { entering: { @@ -49,7 +49,7 @@ const Zoom = React.forwardRef(function Zoom(props, ref) { } = props; const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, getChildRef(children), ref); + const handleRef = useForkRef(nodeRef, getReactNodeRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-material/src/utils/getChildRef.d.ts b/packages/mui-material/src/utils/getChildRef.d.ts deleted file mode 100644 index c7e9c058d3e031..00000000000000 --- a/packages/mui-material/src/utils/getChildRef.d.ts +++ /dev/null @@ -1 +0,0 @@ -export default function getChildRef(child: React.ReactElement): React.Ref | null; diff --git a/packages/mui-material/src/utils/getChildRef.js b/packages/mui-material/src/utils/getChildRef.js deleted file mode 100644 index a1d0c2aec5af0c..00000000000000 --- a/packages/mui-material/src/utils/getChildRef.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from 'react'; - -export default function getChildRef(child) { - if (!child || !React.isValidElement(child)) { - return null; - } - // 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in React 18 - // below check is to ensure 'ref' is accessible in both cases - return child.props.propertyIsEnumerable('ref') ? child.props.ref : child.ref; -} diff --git a/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts b/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts new file mode 100644 index 00000000000000..6da044530bec50 --- /dev/null +++ b/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts @@ -0,0 +1,22 @@ +import * as React from 'react'; + +/** + * Returns the ref of a React node handling differences between React 19 and older versions. + * It will return null if the node is not a valid React element. + * + * @param element React.ReactNode + * @returns React.Ref | null + */ +export default function getReactNodeRef(element: React.ReactNode): React.Ref | null { + if (!element || !React.isValidElement(element)) { + return null; + } + + // 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions + return (element.props as any).propertyIsEnumerable('ref') + ? (element.props as any).ref + : // @ts-expect-error element.ref is not included in the ReactElement type + // We cannot check for it, but isValidElement is true at this point + // https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189 + element.ref; +} diff --git a/packages/mui-utils/src/getReactNodeRef/index.ts b/packages/mui-utils/src/getReactNodeRef/index.ts new file mode 100644 index 00000000000000..4b8dbacb937578 --- /dev/null +++ b/packages/mui-utils/src/getReactNodeRef/index.ts @@ -0,0 +1 @@ +export { default } from './getReactNodeRef'; diff --git a/packages/mui-utils/src/index.ts b/packages/mui-utils/src/index.ts index d4b0d45770f7f6..1b049fdd43c8e9 100644 --- a/packages/mui-utils/src/index.ts +++ b/packages/mui-utils/src/index.ts @@ -49,4 +49,5 @@ export { default as unstable_useSlotProps } from './useSlotProps'; export type { UseSlotPropsParameters, UseSlotPropsResult } from './useSlotProps'; export { default as unstable_resolveComponentProps } from './resolveComponentProps'; export { default as unstable_extractEventHandlers } from './extractEventHandlers'; +export { default as unstable_getReactNodeRef } from './getReactNodeRef'; export * from './types'; diff --git a/packages/mui-utils/src/useForkRef/useForkRef.test.js b/packages/mui-utils/src/useForkRef/useForkRef.test.js index 57a7290a635b48..e45f9d15198648 100644 --- a/packages/mui-utils/src/useForkRef/useForkRef.test.js +++ b/packages/mui-utils/src/useForkRef/useForkRef.test.js @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { createRenderer, screen } from '@mui-internal/test-utils'; import useForkRef from './useForkRef'; +import getReactNodeRef from '../getReactNodeRef'; describe('useForkRef', () => { const { render } = createRenderer(); @@ -47,7 +48,7 @@ describe('useForkRef', () => { it('does nothing if none of the forked branches requires a ref', () => { const Outer = React.forwardRef(function Outer(props, ref) { const { children } = props; - const handleRef = useForkRef(children.ref, ref); + const handleRef = useForkRef(getReactNodeRef(children), ref); return React.cloneElement(children, { ref: handleRef }); }); From 26fe14deefe2dcc5550c377f6b770f65476349e9 Mon Sep 17 00:00:00 2001 From: sai chand <60743144+sai6855@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:50:33 +0530 Subject: [PATCH 3/4] [material-ui] Improve getReactElementRef() utils (#43022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aarón García Hervás --- .../ClickAwayListener/ClickAwayListener.tsx | 4 +- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 4 +- packages/mui-base/src/Portal/Portal.tsx | 8 +++- packages/mui-joy/src/Tooltip/Tooltip.tsx | 4 +- .../ClickAwayListener/ClickAwayListener.tsx | 4 +- packages/mui-material/src/Fade/Fade.js | 4 +- packages/mui-material/src/Grow/Grow.js | 4 +- packages/mui-material/src/Portal/Portal.tsx | 8 +++- packages/mui-material/src/Select/Select.js | 4 +- packages/mui-material/src/Slide/Slide.js | 4 +- packages/mui-material/src/Tooltip/Tooltip.js | 4 +- .../src/Unstable_TrapFocus/FocusTrap.tsx | 4 +- packages/mui-material/src/Zoom/Zoom.js | 4 +- .../getReactElementRef.spec.tsx | 19 +++++++++ .../getReactElementRef.test.tsx | 39 +++++++++++++++++++ .../getReactElementRef/getReactElementRef.ts | 20 ++++++++++ .../mui-utils/src/getReactElementRef/index.ts | 1 + .../src/getReactNodeRef/getReactNodeRef.ts | 22 ----------- .../mui-utils/src/getReactNodeRef/index.ts | 1 - packages/mui-utils/src/index.ts | 2 +- .../src/useForkRef/useForkRef.test.js | 4 +- 21 files changed, 116 insertions(+), 52 deletions(-) create mode 100644 packages/mui-utils/src/getReactElementRef/getReactElementRef.spec.tsx create mode 100644 packages/mui-utils/src/getReactElementRef/getReactElementRef.test.tsx create mode 100644 packages/mui-utils/src/getReactElementRef/getReactElementRef.ts create mode 100644 packages/mui-utils/src/getReactElementRef/index.ts delete mode 100644 packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts delete mode 100644 packages/mui-utils/src/getReactNodeRef/index.ts diff --git a/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx b/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx index a7c7c49148d2d5..a3270f05b34c5c 100644 --- a/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx +++ b/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx @@ -7,7 +7,7 @@ import { unstable_ownerDocument as ownerDocument, unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, - unstable_getReactNodeRef as getReactNodeRef, + unstable_getReactElementRef as getReactElementRef, } from '@mui/utils'; // TODO: return `EventHandlerName extends `on${infer EventName}` ? Lowercase : never` once generatePropTypes runs with TS 4.1 @@ -95,7 +95,7 @@ function ClickAwayListener(props: ClickAwayListenerProps): React.JSX.Element { }; }, []); - const handleRef = useForkRef(getReactNodeRef(children), nodeRef); + const handleRef = useForkRef(getReactElementRef(children), nodeRef); // The handler doesn't take event.defaultPrevented into account: // diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index d17c562746eeed..5888a8031c634f 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -7,7 +7,7 @@ import { elementAcceptingRef, unstable_useForkRef as useForkRef, unstable_ownerDocument as ownerDocument, - unstable_getReactNodeRef as getReactNodeRef, + unstable_getReactElementRef as getReactElementRef, } from '@mui/utils'; import { FocusTrapProps } from './FocusTrap.types'; @@ -153,7 +153,7 @@ function FocusTrap(props: FocusTrapProps): React.JSX.Element { const activated = React.useRef(false); const rootRef = React.useRef(null); - const handleRef = useForkRef(getReactNodeRef(children), rootRef); + const handleRef = useForkRef(getReactElementRef(children), rootRef); const lastKeydown = React.useRef(null); React.useEffect(() => { diff --git a/packages/mui-base/src/Portal/Portal.tsx b/packages/mui-base/src/Portal/Portal.tsx index 89588fa288ce7d..830b229d8eb175 100644 --- a/packages/mui-base/src/Portal/Portal.tsx +++ b/packages/mui-base/src/Portal/Portal.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import getReactNodeRef from '@mui/utils/getReactNodeRef'; +import getReactElementRef from '@mui/utils/getReactElementRef'; import { exactProp, HTMLElementType, @@ -34,7 +34,11 @@ const Portal = React.forwardRef(function Portal( ) { const { children, container, disablePortal = false } = props; const [mountNode, setMountNode] = React.useState>(null); - const handleRef = useForkRef(getReactNodeRef(children), forwardedRef); + + const handleRef = useForkRef( + React.isValidElement(children) ? getReactElementRef(children) : null, + forwardedRef, + ); useEnhancedEffect(() => { if (!disablePortal) { diff --git a/packages/mui-joy/src/Tooltip/Tooltip.tsx b/packages/mui-joy/src/Tooltip/Tooltip.tsx index dd46d08d611e6b..7f48d74360228b 100644 --- a/packages/mui-joy/src/Tooltip/Tooltip.tsx +++ b/packages/mui-joy/src/Tooltip/Tooltip.tsx @@ -11,7 +11,7 @@ import { unstable_useId as useId, unstable_useTimeout as useTimeout, unstable_Timeout as Timeout, - unstable_getReactNodeRef as getReactNodeRef, + unstable_getReactElementRef as getReactElementRef, } from '@mui/utils'; import { Popper, unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; @@ -425,7 +425,7 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { const handleUseRef = useForkRef(setChildNode, ref); const handleFocusRef = useForkRef(focusVisibleRef, handleUseRef); - const handleRef = useForkRef(getReactNodeRef(children), handleFocusRef); + const handleRef = useForkRef(getReactElementRef(children), handleFocusRef); // There is no point in displaying an empty tooltip. if (typeof title !== 'number' && !title) { diff --git a/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx b/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx index 7cc39da363978d..5c6ee0f77cc3ee 100644 --- a/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx +++ b/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx @@ -8,7 +8,7 @@ import { unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, } from '@mui/utils'; -import getReactNodeRef from '@mui/utils/getReactNodeRef'; +import getReactElementRef from '@mui/utils/getReactElementRef'; // TODO: return `EventHandlerName extends `on${infer EventName}` ? Lowercase : never` once generatePropTypes runs with TS 4.1 function mapEventPropToEvent( @@ -96,7 +96,7 @@ function ClickAwayListener(props: ClickAwayListenerProps): JSX.Element { }; }, []); - const handleRef = useForkRef(getReactNodeRef(children), nodeRef); + const handleRef = useForkRef(getReactElementRef(children), nodeRef); // The handler doesn't take event.defaultPrevented into account: // diff --git a/packages/mui-material/src/Fade/Fade.js b/packages/mui-material/src/Fade/Fade.js index d4ae9e144b7f7e..6521768d87c407 100644 --- a/packages/mui-material/src/Fade/Fade.js +++ b/packages/mui-material/src/Fade/Fade.js @@ -3,7 +3,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { Transition } from 'react-transition-group'; import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; -import getReactNodeRef from '@mui/utils/getReactNodeRef'; +import getReactElementRef from '@mui/utils/getReactElementRef'; import useTheme from '../styles/useTheme'; import { reflow, getTransitionProps } from '../transitions/utils'; import useForkRef from '../utils/useForkRef'; @@ -49,7 +49,7 @@ const Fade = React.forwardRef(function Fade(props, ref) { const enableStrictModeCompat = true; const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, getReactNodeRef(children), ref); + const handleRef = useForkRef(nodeRef, getReactElementRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Grow/Grow.js b/packages/mui-material/src/Grow/Grow.js index 7df39963d619ef..0488f1f5810e5c 100644 --- a/packages/mui-material/src/Grow/Grow.js +++ b/packages/mui-material/src/Grow/Grow.js @@ -3,7 +3,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import useTimeout from '@mui/utils/useTimeout'; import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; -import getReactNodeRef from '@mui/utils/getReactNodeRef'; +import getReactElementRef from '@mui/utils/getReactElementRef'; import { Transition } from 'react-transition-group'; import useTheme from '../styles/useTheme'; import { getTransitionProps, reflow } from '../transitions/utils'; @@ -62,7 +62,7 @@ const Grow = React.forwardRef(function Grow(props, ref) { const theme = useTheme(); const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, getReactNodeRef(children), ref); + const handleRef = useForkRef(nodeRef, getReactElementRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Portal/Portal.tsx b/packages/mui-material/src/Portal/Portal.tsx index b6aff42416c348..8bd141d8a5f858 100644 --- a/packages/mui-material/src/Portal/Portal.tsx +++ b/packages/mui-material/src/Portal/Portal.tsx @@ -8,7 +8,7 @@ import { unstable_useEnhancedEffect as useEnhancedEffect, unstable_useForkRef as useForkRef, unstable_setRef as setRef, - unstable_getReactNodeRef as getReactNodeRef, + unstable_getReactElementRef as getReactElementRef, } from '@mui/utils'; import { PortalProps } from './Portal.types'; @@ -34,7 +34,11 @@ const Portal = React.forwardRef(function Portal( ) { const { children, container, disablePortal = false } = props; const [mountNode, setMountNode] = React.useState>(null); - const handleRef = useForkRef(getReactNodeRef(children), forwardedRef); + + const handleRef = useForkRef( + React.isValidElement(children) ? getReactElementRef(children) : null, + forwardedRef, + ); useEnhancedEffect(() => { if (!disablePortal) { diff --git a/packages/mui-material/src/Select/Select.js b/packages/mui-material/src/Select/Select.js index 43717c3fcc7809..8bc83230120001 100644 --- a/packages/mui-material/src/Select/Select.js +++ b/packages/mui-material/src/Select/Select.js @@ -3,7 +3,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import deepmerge from '@mui/utils/deepmerge'; -import getReactNodeRef from '@mui/utils/getReactNodeRef'; +import getReactElementRef from '@mui/utils/getReactElementRef'; import SelectInput from './SelectInput'; import formControlState from '../FormControl/formControlState'; import useFormControl from '../FormControl/useFormControl'; @@ -85,7 +85,7 @@ const Select = React.forwardRef(function Select(inProps, ref) { filled: , }[variant]; - const inputComponentRef = useForkRef(ref, getReactNodeRef(InputComponent)); + const inputComponentRef = useForkRef(ref, getReactElementRef(InputComponent)); return ( diff --git a/packages/mui-material/src/Slide/Slide.js b/packages/mui-material/src/Slide/Slide.js index 452b2d84797a53..3bcdb311e85eab 100644 --- a/packages/mui-material/src/Slide/Slide.js +++ b/packages/mui-material/src/Slide/Slide.js @@ -5,7 +5,7 @@ import { Transition } from 'react-transition-group'; import chainPropTypes from '@mui/utils/chainPropTypes'; import HTMLElementType from '@mui/utils/HTMLElementType'; import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; -import getReactNodeRef from '@mui/utils/getReactNodeRef'; +import getReactElementRef from '@mui/utils/getReactElementRef'; import debounce from '../utils/debounce'; import useForkRef from '../utils/useForkRef'; import useTheme from '../styles/useTheme'; @@ -120,7 +120,7 @@ const Slide = React.forwardRef(function Slide(props, ref) { } = props; const childrenRef = React.useRef(null); - const handleRef = useForkRef(getReactNodeRef(children), childrenRef, ref); + const handleRef = useForkRef(getReactElementRef(children), childrenRef, ref); const normalizedTransitionCallback = (callback) => (isAppearing) => { if (callback) { diff --git a/packages/mui-material/src/Tooltip/Tooltip.js b/packages/mui-material/src/Tooltip/Tooltip.js index ffe278f21ea279..d72c599e6ba863 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.js +++ b/packages/mui-material/src/Tooltip/Tooltip.js @@ -8,7 +8,7 @@ import composeClasses from '@mui/utils/composeClasses'; import { alpha } from '@mui/system/colorManipulator'; import { useRtl } from '@mui/system/RtlProvider'; import appendOwnerState from '@mui/utils/appendOwnerState'; -import getReactNodeRef from '@mui/utils/getReactNodeRef'; +import getReactElementRef from '@mui/utils/getReactElementRef'; import { styled, useTheme } from '../styles'; import { useDefaultProps } from '../DefaultPropsProvider'; import capitalize from '../utils/capitalize'; @@ -486,7 +486,7 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { }; }, [handleClose, open]); - const handleRef = useForkRef(getReactNodeRef(children), focusVisibleRef, setChildNode, ref); + const handleRef = useForkRef(getReactElementRef(children), focusVisibleRef, setChildNode, ref); // There is no point in displaying an empty tooltip. // So we exclude all falsy values, except 0, which is valid. diff --git a/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx b/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx index a76feff24b020b..df0e9476238f71 100644 --- a/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx +++ b/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx @@ -7,7 +7,7 @@ import { elementAcceptingRef, unstable_useForkRef as useForkRef, unstable_ownerDocument as ownerDocument, - unstable_getReactNodeRef as getReactNodeRef, + unstable_getReactElementRef as getReactElementRef, } from '@mui/utils'; import { FocusTrapProps } from './FocusTrap.types'; @@ -145,7 +145,7 @@ function FocusTrap(props: FocusTrapProps): JSX.Element { const activated = React.useRef(false); const rootRef = React.useRef(null); - const handleRef = useForkRef(getReactNodeRef(children), rootRef); + const handleRef = useForkRef(getReactElementRef(children), rootRef); const lastKeydown = React.useRef(null); React.useEffect(() => { diff --git a/packages/mui-material/src/Zoom/Zoom.js b/packages/mui-material/src/Zoom/Zoom.js index ad28f14b3caf6b..62c721b2eec490 100644 --- a/packages/mui-material/src/Zoom/Zoom.js +++ b/packages/mui-material/src/Zoom/Zoom.js @@ -3,7 +3,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { Transition } from 'react-transition-group'; import elementAcceptingRef from '@mui/utils/elementAcceptingRef'; -import getReactNodeRef from '@mui/utils/getReactNodeRef'; +import getReactElementRef from '@mui/utils/getReactElementRef'; import useTheme from '../styles/useTheme'; import { reflow, getTransitionProps } from '../transitions/utils'; import useForkRef from '../utils/useForkRef'; @@ -49,7 +49,7 @@ const Zoom = React.forwardRef(function Zoom(props, ref) { } = props; const nodeRef = React.useRef(null); - const handleRef = useForkRef(nodeRef, getReactNodeRef(children), ref); + const handleRef = useForkRef(nodeRef, getReactElementRef(children), ref); const normalizedTransitionCallback = (callback) => (maybeIsAppearing) => { if (callback) { diff --git a/packages/mui-utils/src/getReactElementRef/getReactElementRef.spec.tsx b/packages/mui-utils/src/getReactElementRef/getReactElementRef.spec.tsx new file mode 100644 index 00000000000000..434cf59fba109a --- /dev/null +++ b/packages/mui-utils/src/getReactElementRef/getReactElementRef.spec.tsx @@ -0,0 +1,19 @@ +import getReactElementRef from '@mui/utils/getReactElementRef'; +import * as React from 'react'; + +// @ts-expect-error +getReactElementRef(false); + +// @ts-expect-error +getReactElementRef(null); + +// @ts-expect-error +getReactElementRef(undefined); + +// @ts-expect-error +getReactElementRef(1); + +// @ts-expect-error +getReactElementRef([
,
]); + +getReactElementRef(
); diff --git a/packages/mui-utils/src/getReactElementRef/getReactElementRef.test.tsx b/packages/mui-utils/src/getReactElementRef/getReactElementRef.test.tsx new file mode 100644 index 00000000000000..d0abe2e3d7ed93 --- /dev/null +++ b/packages/mui-utils/src/getReactElementRef/getReactElementRef.test.tsx @@ -0,0 +1,39 @@ +import { expect } from 'chai'; +import getReactElementRef from '@mui/utils/getReactElementRef'; +import * as React from 'react'; + +describe('getReactElementRef', () => { + it('should return undefined when not used correctly', () => { + // @ts-expect-error + expect(getReactElementRef(false)).to.equal(undefined); + // @ts-expect-error + expect(getReactElementRef()).to.equal(undefined); + // @ts-expect-error + expect(getReactElementRef(1)).to.equal(undefined); + + const children = [
,
]; + // @ts-expect-error + expect(getReactElementRef(children)).to.equal(undefined); + }); + + it('should return the ref of a React element', () => { + const ref = React.createRef(); + const element =
; + expect(getReactElementRef(element)).to.equal(ref); + }); + + it('should return null for a fragment', () => { + const element = ( + +

Hello

+

Hello

+
+ ); + expect(getReactElementRef(element)).to.equal(null); + }); + + it('should return null for element with no ref', () => { + const element =
; + expect(getReactElementRef(element)).to.equal(null); + }); +}); diff --git a/packages/mui-utils/src/getReactElementRef/getReactElementRef.ts b/packages/mui-utils/src/getReactElementRef/getReactElementRef.ts new file mode 100644 index 00000000000000..fbdacefa82dbd1 --- /dev/null +++ b/packages/mui-utils/src/getReactElementRef/getReactElementRef.ts @@ -0,0 +1,20 @@ +import * as React from 'react'; + +/** + * Returns the ref of a React element handling differences between React 19 and older versions. + * It will throw runtime error if the element is not a valid React element. + * + * @param element React.ReactElement + * @returns React.Ref | null | undefined + */ +export default function getReactElementRef( + element: React.ReactElement, +): React.Ref | null | undefined { + // 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions + if (parseInt(React.version, 10) >= 19) { + return element.props?.ref; + } + // @ts-expect-error element.ref is not included in the ReactElement type + // https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189 + return element?.ref; +} diff --git a/packages/mui-utils/src/getReactElementRef/index.ts b/packages/mui-utils/src/getReactElementRef/index.ts new file mode 100644 index 00000000000000..e71d03be6f7988 --- /dev/null +++ b/packages/mui-utils/src/getReactElementRef/index.ts @@ -0,0 +1 @@ +export { default } from './getReactElementRef'; diff --git a/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts b/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts deleted file mode 100644 index 6da044530bec50..00000000000000 --- a/packages/mui-utils/src/getReactNodeRef/getReactNodeRef.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; - -/** - * Returns the ref of a React node handling differences between React 19 and older versions. - * It will return null if the node is not a valid React element. - * - * @param element React.ReactNode - * @returns React.Ref | null - */ -export default function getReactNodeRef(element: React.ReactNode): React.Ref | null { - if (!element || !React.isValidElement(element)) { - return null; - } - - // 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions - return (element.props as any).propertyIsEnumerable('ref') - ? (element.props as any).ref - : // @ts-expect-error element.ref is not included in the ReactElement type - // We cannot check for it, but isValidElement is true at this point - // https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189 - element.ref; -} diff --git a/packages/mui-utils/src/getReactNodeRef/index.ts b/packages/mui-utils/src/getReactNodeRef/index.ts deleted file mode 100644 index 4b8dbacb937578..00000000000000 --- a/packages/mui-utils/src/getReactNodeRef/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './getReactNodeRef'; diff --git a/packages/mui-utils/src/index.ts b/packages/mui-utils/src/index.ts index 1b049fdd43c8e9..3a1aaabf05b8a8 100644 --- a/packages/mui-utils/src/index.ts +++ b/packages/mui-utils/src/index.ts @@ -49,5 +49,5 @@ export { default as unstable_useSlotProps } from './useSlotProps'; export type { UseSlotPropsParameters, UseSlotPropsResult } from './useSlotProps'; export { default as unstable_resolveComponentProps } from './resolveComponentProps'; export { default as unstable_extractEventHandlers } from './extractEventHandlers'; -export { default as unstable_getReactNodeRef } from './getReactNodeRef'; +export { default as unstable_getReactElementRef } from './getReactElementRef'; export * from './types'; diff --git a/packages/mui-utils/src/useForkRef/useForkRef.test.js b/packages/mui-utils/src/useForkRef/useForkRef.test.js index e45f9d15198648..4b1a688b1abb0e 100644 --- a/packages/mui-utils/src/useForkRef/useForkRef.test.js +++ b/packages/mui-utils/src/useForkRef/useForkRef.test.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { createRenderer, screen } from '@mui-internal/test-utils'; import useForkRef from './useForkRef'; -import getReactNodeRef from '../getReactNodeRef'; +import getReactElementRef from '../getReactElementRef'; describe('useForkRef', () => { const { render } = createRenderer(); @@ -48,7 +48,7 @@ describe('useForkRef', () => { it('does nothing if none of the forked branches requires a ref', () => { const Outer = React.forwardRef(function Outer(props, ref) { const { children } = props; - const handleRef = useForkRef(getReactNodeRef(children), ref); + const handleRef = useForkRef(getReactElementRef(children), ref); return React.cloneElement(children, { ref: handleRef }); }); From e6596a55079ba87849a92d077a0961c383f98794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aar=C3=B3n=20Garc=C3=ADa=20Herv=C3=A1s?= Date: Tue, 8 Oct 2024 09:53:10 +0200 Subject: [PATCH 4/4] [utils] Make getReactElementRef React 19 compatible (#44034) --- .../src/getReactElementRef/getReactElementRef.test.tsx | 8 ++++---- .../src/getReactElementRef/getReactElementRef.ts | 10 ++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/mui-utils/src/getReactElementRef/getReactElementRef.test.tsx b/packages/mui-utils/src/getReactElementRef/getReactElementRef.test.tsx index d0abe2e3d7ed93..b7bfbd3da5b67b 100644 --- a/packages/mui-utils/src/getReactElementRef/getReactElementRef.test.tsx +++ b/packages/mui-utils/src/getReactElementRef/getReactElementRef.test.tsx @@ -5,15 +5,15 @@ import * as React from 'react'; describe('getReactElementRef', () => { it('should return undefined when not used correctly', () => { // @ts-expect-error - expect(getReactElementRef(false)).to.equal(undefined); + expect(getReactElementRef(false)).to.equal(null); // @ts-expect-error - expect(getReactElementRef()).to.equal(undefined); + expect(getReactElementRef()).to.equal(null); // @ts-expect-error - expect(getReactElementRef(1)).to.equal(undefined); + expect(getReactElementRef(1)).to.equal(null); const children = [
,
]; // @ts-expect-error - expect(getReactElementRef(children)).to.equal(undefined); + expect(getReactElementRef(children)).to.equal(null); }); it('should return the ref of a React element', () => { diff --git a/packages/mui-utils/src/getReactElementRef/getReactElementRef.ts b/packages/mui-utils/src/getReactElementRef/getReactElementRef.ts index fbdacefa82dbd1..75394cbfa0ae8e 100644 --- a/packages/mui-utils/src/getReactElementRef/getReactElementRef.ts +++ b/packages/mui-utils/src/getReactElementRef/getReactElementRef.ts @@ -5,16 +5,14 @@ import * as React from 'react'; * It will throw runtime error if the element is not a valid React element. * * @param element React.ReactElement - * @returns React.Ref | null | undefined + * @returns React.Ref | null */ -export default function getReactElementRef( - element: React.ReactElement, -): React.Ref | null | undefined { +export default function getReactElementRef(element: React.ReactElement): React.Ref | null { // 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions if (parseInt(React.version, 10) >= 19) { - return element.props?.ref; + return (element?.props as any)?.ref || null; } // @ts-expect-error element.ref is not included in the ReactElement type // https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189 - return element?.ref; + return element?.ref || null; }