Skip to content

Commit 9283b27

Browse files
LFDanLumischnicsnowystinger
authored
Provide nodeRef to Transition to fix StrictMode warning (#3865)
* Update react-transition-group * Pass nodeRef into Transition * WIP * fix docs build * Remove a few ignores and explain the last remaining one * fixing some overlay refs passed to Transition got rid of clone children in tooltip also since it would break if wrapper existed around the tooltip component * fixing transition for Modal and Tray needed to provide an actual DOM ref to OpenTransition nodeRef * actually use wrapper refs * fixing actionbar ref to Transition * adding wrapper to attach transition nodeRef technically we could attach it to any DOM node in the Modal/Tray/Popover since we dont have a transition on enter, but for correctness with reflow/.scrollTop we want it as the wrapping element * fixing rebase * fix lint * fix yarn.lock * fix Tray and Modal types * fix TS strict For some reason, Ref doesnt break TS strict linter but RefObject does... * adding comments * remove ref from Underlay Co-authored-by: Niklas Mischkulnig <[email protected]> Co-authored-by: Rob Snow <[email protected]>
1 parent bb9f65f commit 9283b27

File tree

7 files changed

+52
-40
lines changed

7 files changed

+52
-40
lines changed

packages/@react-spectrum/actionbar/src/ActionBar.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {FocusScope} from '@react-aria/focus';
2121
// @ts-ignore
2222
import intlMessages from '../intl/*.json';
2323
import {OpenTransition} from '@react-spectrum/overlays';
24-
import React, {ReactElement, useEffect, useRef} from 'react';
24+
import React, {ReactElement, Ref, useEffect, useRef} from 'react';
2525
import {SpectrumActionBarProps} from '@react-types/actionbar';
2626
import styles from './actionbar.css';
2727
import {Text} from '@react-spectrum/text';
@@ -31,13 +31,15 @@ import {useProviderProps} from '@react-spectrum/provider';
3131

3232
function ActionBar<T extends object>(props: SpectrumActionBarProps<T>, ref: DOMRef<HTMLDivElement>) {
3333
let isOpen = props.selectedItemCount !== 0;
34+
let domRef = useDOMRef(ref);
3435

3536
return (
3637
<OpenTransition
38+
nodeRef={domRef}
3739
in={isOpen}
3840
mountOnEnter
3941
unmountOnExit>
40-
<ActionBarInnerWithRef {...props} ref={ref} />
42+
<ActionBarInnerWithRef {...props} ref={domRef} />
4143
</OpenTransition>
4244
);
4345
}
@@ -46,7 +48,7 @@ interface ActionBarInnerProps<T> extends SpectrumActionBarProps<T> {
4648
isOpen?: boolean
4749
}
4850

49-
function ActionBarInner<T>(props: ActionBarInnerProps<T>, ref: DOMRef<HTMLDivElement>) {
51+
function ActionBarInner<T>(props: ActionBarInnerProps<T>, ref: Ref<HTMLDivElement>) {
5052
props = useProviderProps(props);
5153

5254
let {
@@ -59,7 +61,6 @@ function ActionBarInner<T>(props: ActionBarInnerProps<T>, ref: DOMRef<HTMLDivEle
5961
} = props;
6062

6163
let {styleProps} = useStyleProps(props);
62-
let domRef = useDOMRef(ref);
6364
let stringFormatter = useLocalizedStringFormatter(intlMessages);
6465

6566
// Store the last count greater than zero in a ref so that we can retain it while rendering the fade-out animation.
@@ -88,7 +89,7 @@ function ActionBarInner<T>(props: ActionBarInnerProps<T>, ref: DOMRef<HTMLDivEle
8889
{...filterDOMProps(props)}
8990
{...styleProps}
9091
{...keyboardProps}
91-
ref={domRef}
92+
ref={ref}
9293
className={classNames(
9394
styles,
9495
'react-spectrum-ActionBar', {
@@ -127,7 +128,7 @@ function ActionBarInner<T>(props: ActionBarInnerProps<T>, ref: DOMRef<HTMLDivEle
127128
);
128129
}
129130

130-
const ActionBarInnerWithRef = React.forwardRef(ActionBarInner) as <T>(props: SpectrumActionBarProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReturnType<typeof ActionBarInner>;
131+
const ActionBarInnerWithRef = React.forwardRef(ActionBarInner) as <T>(props: ActionBarInnerProps<T> & {ref?: Ref<HTMLDivElement>}) => ReactElement;
131132

132133
/**
133134
* TODO: Add description of component here.

packages/@react-spectrum/overlays/src/Modal.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,29 @@ import {Overlay} from './Overlay';
1818
import {OverlayProps} from '@react-types/overlays';
1919
import {OverlayTriggerState} from '@react-stately/overlays';
2020
import overrideStyles from './overlays.css';
21-
import React, {forwardRef, ReactNode, RefObject} from 'react';
21+
import React, {forwardRef, MutableRefObject, ReactNode, RefObject, useRef} from 'react';
2222
import {Underlay} from './Underlay';
2323
import {useViewportSize} from '@react-aria/utils';
2424

25-
interface ModalProps extends AriaModalOverlayProps, StyleProps, OverlayProps {
25+
interface ModalProps extends AriaModalOverlayProps, StyleProps, Omit<OverlayProps, 'nodeRef'> {
2626
children: ReactNode,
2727
state: OverlayTriggerState,
2828
type?: 'modal' | 'fullscreen' | 'fullscreenTakeover'
2929
}
3030

3131
interface ModalWrapperProps extends ModalProps {
32-
isOpen?: boolean
32+
isOpen?: boolean,
33+
wrapperRef: MutableRefObject<HTMLDivElement>
3334
}
3435

3536
function Modal(props: ModalProps, ref: DOMRef<HTMLDivElement>) {
3637
let {children, state, ...otherProps} = props;
3738
let domRef = useDOMRef(ref);
39+
let wrapperRef = useRef<HTMLDivElement>(null);
3840

3941
return (
40-
<Overlay {...otherProps} isOpen={state.isOpen}>
41-
<ModalWrapper {...props} ref={domRef}>
42+
<Overlay {...otherProps} isOpen={state.isOpen} nodeRef={wrapperRef}>
43+
<ModalWrapper {...props} wrapperRef={wrapperRef} ref={domRef}>
4244
{children}
4345
</ModalWrapper>
4446
</Overlay>
@@ -51,10 +53,9 @@ let typeMap = {
5153
};
5254

5355
let ModalWrapper = forwardRef(function (props: ModalWrapperProps, ref: RefObject<HTMLDivElement>) {
54-
let {type, children, state, isOpen} = props;
56+
let {type, children, state, isOpen, wrapperRef} = props;
5557
let typeVariant = typeMap[type];
5658
let {styleProps} = useStyleProps(props);
57-
5859
let {modalProps, underlayProps} = useModalOverlay(props, state, ref);
5960

6061
let wrapperClassName = classNames(
@@ -87,8 +88,9 @@ let ModalWrapper = forwardRef(function (props: ModalWrapperProps, ref: RefObject
8788
'--spectrum-visual-viewport-height': viewport.height + 'px'
8889
};
8990

91+
// Attach Transition's nodeRef to outer most wrapper for node.reflow: https://github.com/reactjs/react-transition-group/blob/c89f807067b32eea6f68fd6c622190d88ced82e2/src/Transition.js#L231
9092
return (
91-
<>
93+
<div ref={wrapperRef}>
9294
<Underlay {...underlayProps} isOpen={isOpen} />
9395
<div className={wrapperClassName} style={style}>
9496
<div
@@ -100,7 +102,7 @@ let ModalWrapper = forwardRef(function (props: ModalWrapperProps, ref: RefObject
100102
{children}
101103
</div>
102104
</div>
103-
</>
105+
</div>
104106
);
105107
});
106108

packages/@react-spectrum/overlays/src/Overlay.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import React, {useCallback, useState} from 'react';
1818
import {Overlay as ReactAriaOverlay} from '@react-aria/overlays';
1919

2020
function Overlay(props: OverlayProps, ref: DOMRef<HTMLDivElement>) {
21-
let {children, isOpen, container, onEnter, onEntering, onEntered, onExit, onExiting, onExited} = props;
21+
let {children, isOpen, container, onEnter, onEntering, onEntered, onExit, onExiting, onExited, nodeRef} = props;
2222
let [exited, setExited] = useState(!isOpen);
2323

2424
let handleEntered = useCallback(() => {
@@ -53,7 +53,8 @@ function Overlay(props: OverlayProps, ref: DOMRef<HTMLDivElement>) {
5353
onExited={handleExited}
5454
onEnter={onEnter}
5555
onEntering={onEntering}
56-
onEntered={handleEntered}>
56+
onEntered={handleEntered}
57+
nodeRef={nodeRef}>
5758
{children}
5859
</OpenTransition>
5960
</Provider>

packages/@react-spectrum/overlays/src/Popover.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {DOMRef, StyleProps} from '@react-types/shared';
1616
import {Overlay} from './Overlay';
1717
import {OverlayTriggerState} from '@react-stately/overlays';
1818
import overrideStyles from './overlays.css';
19-
import React, {forwardRef, ReactNode, RefObject, useRef, useState} from 'react';
19+
import React, {forwardRef, MutableRefObject, ReactNode, RefObject, useRef, useState} from 'react';
2020
import styles from '@adobe/spectrum-css-temp/components/popover/vars.css';
2121
import {Underlay} from './Underlay';
2222
import {useLayoutEffect} from '@react-aria/utils';
@@ -28,7 +28,8 @@ interface PopoverProps extends Omit<AriaPopoverProps, 'popoverRef' | 'maxHeight'
2828
}
2929

3030
interface PopoverWrapperProps extends PopoverProps {
31-
isOpen?: boolean
31+
isOpen?: boolean,
32+
wrapperRef: MutableRefObject<HTMLDivElement>
3233
}
3334

3435
/**
@@ -52,10 +53,11 @@ function Popover(props: PopoverProps, ref: DOMRef<HTMLDivElement>) {
5253
...otherProps
5354
} = props;
5455
let domRef = useDOMRef(ref);
56+
let wrapperRef = useRef<HTMLDivElement>(null);
5557

5658
return (
57-
<Overlay {...otherProps} isOpen={state.isOpen}>
58-
<PopoverWrapper ref={domRef} {...props}>
59+
<Overlay {...otherProps} isOpen={state.isOpen} nodeRef={wrapperRef}>
60+
<PopoverWrapper ref={domRef} {...props} wrapperRef={wrapperRef}>
5961
{children}
6062
</PopoverWrapper>
6163
</Overlay>
@@ -68,7 +70,8 @@ const PopoverWrapper = forwardRef((props: PopoverWrapperProps, ref: RefObject<HT
6870
isOpen,
6971
hideArrow,
7072
isNonModal,
71-
state
73+
state,
74+
wrapperRef
7275
} = props;
7376
let {styleProps} = useStyleProps(props);
7477

@@ -78,8 +81,9 @@ const PopoverWrapper = forwardRef((props: PopoverWrapperProps, ref: RefObject<HT
7881
maxHeight: null
7982
}, state);
8083

84+
// Attach Transition's nodeRef to outer most wrapper for node.reflow: https://github.com/reactjs/react-transition-group/blob/c89f807067b32eea6f68fd6c622190d88ced82e2/src/Transition.js#L231
8185
return (
82-
<>
86+
<div ref={wrapperRef}>
8387
{!isNonModal && <Underlay isTransparent {...underlayProps} isOpen={isOpen} /> }
8488
<div
8589
{...styleProps}
@@ -115,7 +119,7 @@ const PopoverWrapper = forwardRef((props: PopoverWrapperProps, ref: RefObject<HT
115119
)}
116120
<DismissButton onDismiss={state.close} />
117121
</div>
118-
</>
122+
</div>
119123
);
120124
});
121125

packages/@react-spectrum/overlays/src/Tray.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,30 @@ import {Overlay} from './Overlay';
1717
import {OverlayProps} from '@react-types/overlays';
1818
import {OverlayTriggerState} from '@react-stately/overlays';
1919
import overrideStyles from './overlays.css';
20-
import React, {forwardRef, ReactNode, RefObject} from 'react';
20+
import React, {forwardRef, MutableRefObject, ReactNode, RefObject, useRef} from 'react';
2121
import trayStyles from '@adobe/spectrum-css-temp/components/tray/vars.css';
2222
import {Underlay} from './Underlay';
2323
import {useViewportSize} from '@react-aria/utils';
2424

25-
interface TrayProps extends AriaModalOverlayProps, StyleProps, OverlayProps {
25+
interface TrayProps extends AriaModalOverlayProps, StyleProps, Omit<OverlayProps, 'nodeRef'> {
2626
children: ReactNode,
2727
state: OverlayTriggerState,
2828
isFixedHeight?: boolean
2929
}
3030

3131
interface TrayWrapperProps extends TrayProps {
32-
isOpen?: boolean
32+
isOpen?: boolean,
33+
wrapperRef: MutableRefObject<HTMLDivElement>
3334
}
3435

3536
function Tray(props: TrayProps, ref: DOMRef<HTMLDivElement>) {
3637
let {children, state, ...otherProps} = props;
3738
let domRef = useDOMRef(ref);
39+
let wrapperRef = useRef<HTMLDivElement>(null);
3840

3941
return (
40-
<Overlay {...otherProps} isOpen={state.isOpen}>
41-
<TrayWrapper {...props} ref={domRef}>
42+
<Overlay {...otherProps} isOpen={state.isOpen} nodeRef={wrapperRef}>
43+
<TrayWrapper {...props} wrapperRef={wrapperRef} ref={domRef}>
4244
{children}
4345
</TrayWrapper>
4446
</Overlay>
@@ -50,7 +52,8 @@ let TrayWrapper = forwardRef(function (props: TrayWrapperProps, ref: RefObject<H
5052
children,
5153
isOpen,
5254
isFixedHeight,
53-
state
55+
state,
56+
wrapperRef
5457
} = props;
5558
let {styleProps} = useStyleProps(props);
5659

@@ -91,8 +94,9 @@ let TrayWrapper = forwardRef(function (props: TrayWrapperProps, ref: RefObject<H
9194
styleProps.className
9295
);
9396

97+
// Attach Transition's nodeRef to outer most wrapper for node.reflow: https://github.com/reactjs/react-transition-group/blob/c89f807067b32eea6f68fd6c622190d88ced82e2/src/Transition.js#L231
9498
return (
95-
<>
99+
<div ref={wrapperRef}>
96100
<Underlay {...underlayProps} isOpen={isOpen} />
97101
<div className={wrapperClassName} style={wrapperStyle}>
98102
<div
@@ -106,7 +110,7 @@ let TrayWrapper = forwardRef(function (props: TrayWrapperProps, ref: RefObject<H
106110
<DismissButton onDismiss={state.close} />
107111
</div>
108112
</div>
109-
</>
113+
</div>
110114
);
111115
});
112116

packages/@react-spectrum/tooltip/src/TooltipTrigger.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ function TooltipTrigger(props: SpectrumTooltipTriggerProps) {
3131
trigger: triggerAction
3232
} = props;
3333

34-
let [trigger, tooltip] = React.Children.toArray(children);
35-
34+
let [trigger, tooltip] = React.Children.toArray(children) as [ReactElement, ReactElement];
3635
let state = useTooltipTriggerState(props);
3736

3837
let tooltipTriggerRef = useRef<HTMLElement>();
@@ -68,7 +67,7 @@ function TooltipTrigger(props: SpectrumTooltipTriggerProps) {
6867
arrowProps,
6968
...tooltipProps
7069
}}>
71-
<Overlay isOpen={state.isOpen}>
70+
<Overlay isOpen={state.isOpen} nodeRef={overlayRef}>
7271
{tooltip}
7372
</Overlay>
7473
</TooltipContext.Provider>

packages/@react-types/overlays/src/index.d.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {HTMLAttributes, ReactElement, ReactNode} from 'react';
13+
import {HTMLAttributes, MutableRefObject, ReactElement, ReactNode} from 'react';
1414
import {StyleProps} from '@react-types/shared';
1515

1616
export type Placement = 'bottom' | 'bottom left' | 'bottom right' | 'bottom start' | 'bottom end' |
@@ -71,18 +71,19 @@ export interface OverlayProps {
7171
onEntered?: () => void,
7272
onExit?: () => void,
7373
onExiting?: () => void,
74-
onExited?: () => void
74+
onExited?: () => void,
75+
nodeRef: MutableRefObject<HTMLElement>
7576
}
7677

77-
export interface ModalProps extends StyleProps, OverlayProps {
78+
export interface ModalProps extends StyleProps, Omit<OverlayProps, 'nodeRef'> {
7879
children: ReactElement,
7980
isOpen?: boolean,
8081
onClose?: () => void,
8182
type?: 'modal' | 'fullscreen' | 'fullscreenTakeover',
8283
isDismissable?: boolean
8384
}
8485

85-
export interface PopoverProps extends StyleProps, OverlayProps {
86+
export interface PopoverProps extends StyleProps, Omit<OverlayProps, 'nodeRef'> {
8687
children: ReactNode,
8788
placement?: PlacementAxis,
8889
arrowProps?: HTMLAttributes<HTMLElement>,
@@ -94,7 +95,7 @@ export interface PopoverProps extends StyleProps, OverlayProps {
9495
isDismissable?: boolean
9596
}
9697

97-
export interface TrayProps extends StyleProps, OverlayProps {
98+
export interface TrayProps extends StyleProps, Omit<OverlayProps, 'nodeRef'> {
9899
children: ReactElement,
99100
isOpen?: boolean,
100101
onClose?: () => void,

0 commit comments

Comments
 (0)