Skip to content

Commit 0484ac5

Browse files
refactor(Toolbar): make Toolbar SSR ready (#4117)
Part of #4091
1 parent 6b8b45a commit 0484ac5

File tree

7 files changed

+72
-74
lines changed

7 files changed

+72
-74
lines changed

packages/main/src/components/ActionSheet/index.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
import { isPhone } from '@ui5/webcomponents-base/dist/Device.js';
44
import { useI18nBundle, useSyncRef } from '@ui5/webcomponents-react-base';
55
import { clsx } from 'clsx';
6-
import React, { forwardRef, ReactElement, useEffect, useReducer, useRef, useState } from 'react';
6+
import React, { forwardRef, ReactElement, useReducer, useRef } from 'react';
77
import { createPortal } from 'react-dom';
88
import { createUseStyles } from 'react-jss';
99
import { ButtonDesign } from '../../enums';
1010
import { AVAILABLE_ACTIONS, CANCEL, X_OF_Y } from '../../i18n/i18n-defaults';
1111
import { addCustomCSSWithScoping } from '../../internal/addCustomCSSWithScoping';
12+
import { useCanRenderPortal } from '../../internal/ssr';
1213
import { flattenFragments, isSSR } from '../../internal/utils';
1314
import { CustomThemingParameters } from '../../themes/CustomVariables';
1415
import { UI5WCSlotsNode } from '../../types';
@@ -164,10 +165,10 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
164165
const childrenArrayLength = childrenToRender.length;
165166
const childrenLength = isPhone() && showCancelButton ? childrenArrayLength + 1 : childrenArrayLength;
166167

167-
const [canRender, setCanRender] = useState(false);
168-
useEffect(() => {
169-
setCanRender(true);
170-
}, []);
168+
const canRenderPortal = useCanRenderPortal();
169+
if (!canRenderPortal) {
170+
return null;
171+
}
171172

172173
const handleCancelBtnClick = () => {
173174
popoverRef.current.close();
@@ -218,10 +219,6 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
218219
}
219220
};
220221

221-
if (!canRender) {
222-
return null;
223-
}
224-
225222
const displayHeader = isPhone();
226223
return createPortal(
227224
<ResponsivePopover

packages/main/src/components/MessageViewButton/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { ThemingParameters } from '@ui5/webcomponents-react-base';
88
import { clsx } from 'clsx';
99
import React, { forwardRef } from 'react';
1010
import { createUseStyles } from 'react-jss';
11-
import { Button, ButtonDomRef, ButtonPropTypes } from '../..';
1211
import { ValueState } from '../../enums';
12+
import { Button, ButtonDomRef, ButtonPropTypes } from '../../webComponents';
1313

1414
const buttonStyles = Object.values(ValueState).reduce((acc, cur) => {
1515
let cssType;

packages/main/src/components/Toolbar/OverflowPopover.tsx

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import iconOverflow from '@ui5/webcomponents-icons/dist/overflow.js';
22
import { Device, useSyncRef } from '@ui5/webcomponents-react-base';
33
import { clsx } from 'clsx';
4-
import React, { cloneElement, FC, ReactElement, ReactNode, Ref, useCallback, useEffect, useRef, useState } from 'react';
4+
import React, { cloneElement, FC, ReactElement, ReactNode, Ref, useEffect, useRef, useState } from 'react';
55
import { createPortal } from 'react-dom';
66
import { ButtonDesign, PopoverPlacementType } from '../../enums';
77
import { OverflowPopoverContext } from '../../internal/OverflowPopoverContext';
8+
import { useCanRenderPortal } from '../../internal/ssr';
89
import { stopPropagation } from '../../internal/stopPropagation';
910
import { getUi5TagWithSuffix } from '../../internal/utils';
1011
import {
@@ -76,25 +77,6 @@ export const OverflowPopover: FC<OverflowPopoverProps> = (props: OverflowPopover
7677
setPressed(false);
7778
};
7879

79-
const renderChildren = useCallback((): ReactNode[] => {
80-
return children.map((item, index) => {
81-
if (index > lastVisibleIndex && index > numberOfAlwaysVisibleItems - 1) {
82-
// @ts-expect-error: if type is not defined, it's not a spacer
83-
if (item.type?.displayName === 'ToolbarSeparator') {
84-
return cloneElement(item as ReactElement, {
85-
style: {
86-
height: '0.0625rem',
87-
margin: '0.375rem 0.1875rem',
88-
width: '100%'
89-
}
90-
});
91-
}
92-
return item;
93-
}
94-
return null;
95-
});
96-
}, [children, lastVisibleIndex]);
97-
9880
useEffect(() => {
9981
const tagName = getUi5TagWithSuffix('ui5-toggle-button');
10082
customElements.whenDefined(tagName).then(() => {
@@ -113,6 +95,8 @@ export const OverflowPopover: FC<OverflowPopoverProps> = (props: OverflowPopover
11395
}
11496
};
11597

98+
const canRenderPortal = useCanRenderPortal();
99+
116100
return (
117101
<OverflowPopoverContext.Provider value={{ inPopover: true }}>
118102
{overflowButton ? (
@@ -129,24 +113,40 @@ export const OverflowPopover: FC<OverflowPopoverProps> = (props: OverflowPopover
129113
data-component-name="ToolbarOverflowButton"
130114
/>
131115
)}
132-
{createPortal(
133-
<Popover
134-
data-component-name="ToolbarOverflowPopover"
135-
className={clsx(classes.popover, isPhone && classes.popoverPhone)}
136-
placementType={PopoverPlacementType.Bottom}
137-
ref={componentRef}
138-
open={pressed}
139-
onAfterClose={handleClose}
140-
onBeforeOpen={handleBeforeOpen}
141-
onAfterOpen={handleAfterOpen}
142-
hideArrow
143-
>
144-
<div className={classes.popoverContent} ref={overflowContentRef}>
145-
{renderChildren()}
146-
</div>
147-
</Popover>,
148-
portalContainer
149-
)}
116+
{canRenderPortal &&
117+
createPortal(
118+
<Popover
119+
data-component-name="ToolbarOverflowPopover"
120+
className={clsx(classes.popover, isPhone && classes.popoverPhone)}
121+
placementType={PopoverPlacementType.Bottom}
122+
ref={componentRef}
123+
open={pressed}
124+
onAfterClose={handleClose}
125+
onBeforeOpen={handleBeforeOpen}
126+
onAfterOpen={handleAfterOpen}
127+
hideArrow
128+
>
129+
<div className={classes.popoverContent} ref={overflowContentRef}>
130+
{children.map((item, index) => {
131+
if (index > lastVisibleIndex && index > numberOfAlwaysVisibleItems - 1) {
132+
// @ts-expect-error: if type is not defined, it's not a spacer
133+
if (item.type?.displayName === 'ToolbarSeparator') {
134+
return cloneElement(item as ReactElement, {
135+
style: {
136+
height: '0.0625rem',
137+
margin: '0.375rem 0.1875rem',
138+
width: '100%'
139+
}
140+
});
141+
}
142+
return item;
143+
}
144+
return null;
145+
})}
146+
</div>
147+
</Popover>,
148+
portalContainer ?? document.body
149+
)}
150150
</OverflowPopoverContext.Provider>
151151
);
152152
};

packages/main/src/components/Toolbar/index.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,16 @@ const OVERFLOW_BUTTON_WIDTH = 36 + 8 + 8; // width + padding end + spacing start
118118
const Toolbar = forwardRef<HTMLDivElement, ToolbarPropTypes>((props, ref) => {
119119
const {
120120
children,
121-
toolbarStyle,
122-
design,
123-
active,
121+
toolbarStyle = ToolbarStyle.Standard,
122+
design = ToolbarDesign.Auto,
123+
active = false,
124124
style,
125125
className,
126126
onClick,
127127
slot,
128-
as,
128+
as = 'div',
129129
portalContainer,
130-
numberOfAlwaysVisibleItems,
130+
numberOfAlwaysVisibleItems = 0,
131131
onOverflowChange,
132132
overflowPopoverRef,
133133
overflowButton,
@@ -244,19 +244,19 @@ const Toolbar = forwardRef<HTMLDivElement, ToolbarPropTypes>((props, ref) => {
244244
}
245245
setLastVisibleIndex(lastIndex);
246246
});
247-
}, [outerContainer.current, controlMetaData.current, setLastVisibleIndex, childrenWithRef, overflowNeeded]);
248-
249-
const observer = useRef(new ResizeObserver(calculateVisibleItems));
247+
}, []);
250248

251249
useEffect(() => {
250+
const observer = new ResizeObserver(calculateVisibleItems);
251+
252252
if (outerContainer.current) {
253-
observer.current.observe(outerContainer.current);
253+
observer.observe(outerContainer.current);
254254
}
255255
return () => {
256256
cancelAnimationFrame(requestAnimationFrameRef.current);
257-
observer.current.disconnect();
257+
observer.disconnect();
258258
};
259-
}, [outerContainer.current]);
259+
}, [calculateVisibleItems]);
260260

261261
useIsomorphicLayoutEffect(() => {
262262
calculateVisibleItems();
@@ -350,14 +350,5 @@ const Toolbar = forwardRef<HTMLDivElement, ToolbarPropTypes>((props, ref) => {
350350
);
351351
});
352352

353-
Toolbar.defaultProps = {
354-
as: 'div',
355-
toolbarStyle: ToolbarStyle.Standard,
356-
design: ToolbarDesign.Auto,
357-
active: false,
358-
portalContainer: document.body,
359-
numberOfAlwaysVisibleItems: 0
360-
};
361-
362353
Toolbar.displayName = 'Toolbar';
363354
export { Toolbar };

packages/main/src/internal/ssr.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { EffectCallback, useEffect, useState } from 'react';
2+
3+
export function useServerSideEffect(effect: EffectCallback) {
4+
useEffect(effect, []);
5+
}
6+
7+
export function useCanRenderPortal() {
8+
const [allowed, setAllowed] = useState(false);
9+
10+
useEffect(() => {
11+
setAllowed(true);
12+
}, []);
13+
14+
return allowed;
15+
}

packages/main/src/internal/useServerSideEffect.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/main/src/internal/withWebComponent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import React, {
1313
} from 'react';
1414
import { CommonProps } from '../interfaces/CommonProps';
1515
import { Ui5DomRef } from '../interfaces/Ui5DomRef';
16-
import { useServerSideEffect } from './useServerSideEffect';
16+
import { useServerSideEffect } from './ssr';
1717
import { camelToKebabCase, capitalizeFirstLetter, kebabToCamelCase } from './utils';
1818

1919
const createEventPropName = (eventName) => `on${capitalizeFirstLetter(kebabToCamelCase(eventName))}`;

0 commit comments

Comments
 (0)