Skip to content

Commit 4e90c5c

Browse files
authored
fix: overflowManager should always dispatch initial state (#27756)
* fix: overflowManager should always dispatch initial state Fixes #27656 which was caused by a priority queue edge case. On initial mount the resize observer will always run. If there is already overflow then the tops of the visibility queues will be different from the initial state (all items are considered visible by default). In the cause where there is no overflow initially the queue tops will not change, which does not trigger a dispatch to the react bindings so the `useIsOverflowItemVisible` hook will always return `false` until overflow occurs. The fix is quite simple: set the initial value of flag `forceDispatch` to be `true` which will always trigger a dispatch for initial mount. * changefile
1 parent b83d941 commit 4e90c5c

File tree

5 files changed

+74
-15
lines changed

5 files changed

+74
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix: overflowManager should always dispatch initial state",
4+
"packageName": "@fluentui/priority-overflow",
5+
"email": "lingfangao@hotmail.com",
6+
"dependentChangeType": "patch"
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "refactor: Consolidate all overflow state into one object",
4+
"packageName": "@fluentui/react-overflow",
5+
"email": "lingfangao@hotmail.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/priority-overflow/src/overflowManager.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export function createOverflowManager(): OverflowManager {
1212
// Set as true when resize observer is observing
1313
let observing = false;
1414
// If true, next update will dispatch to onUpdateOverflow even if queue top states don't change
15-
let forceDispatch = false;
15+
// Initially true to force dispatch on first mount
16+
let forceDispatch = true;
1617
const options: Required<ObserveOptions> = {
1718
padding: 10,
1819
overflowAxis: 'horizontal',

packages/react-components/react-overflow/src/Overflow.cy.tsx

+32
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
useIsOverflowGroupVisible,
99
useOverflowMenu,
1010
useOverflowContext,
11+
useIsOverflowItemVisible,
1112
} from '@fluentui/react-overflow';
1213
import { Portal } from '@fluentui/react-portal';
1314

@@ -539,4 +540,35 @@ describe('Overflow', () => {
539540
setContainerSize(500);
540541
cy.contains('Update priority').click().get('#foo-visibility').should('have.text', 'visible');
541542
});
543+
544+
it('Should have correct initial visibility state', () => {
545+
const mapHelper = new Array(10).fill(0).map((_, i) => i);
546+
const Assert = () => {
547+
const isVisible = mapHelper.map(i => {
548+
// eslint-disable-next-line react-hooks/rules-of-hooks
549+
return useIsOverflowItemVisible(i.toString());
550+
});
551+
552+
if (isVisible.every(x => x)) {
553+
return <span data-passed="true" />;
554+
}
555+
556+
return null;
557+
};
558+
559+
mount(
560+
<Container minimumVisible={5}>
561+
{mapHelper.map(i => (
562+
<Item key={i} id={i.toString()}>
563+
{i}
564+
</Item>
565+
))}
566+
<Menu />
567+
<Assert />
568+
</Container>,
569+
);
570+
571+
setContainerSize(500);
572+
cy.get('[data-passed="true"]').should('exist');
573+
});
542574
});

packages/react-components/react-overflow/src/components/Overflow.tsx

+26-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import { OverflowContext } from '../overflowContext';
77
import { updateVisibilityAttribute, useOverflowContainer } from '../useOverflowContainer';
88
import { useOverflowStyles } from './useOverflowStyles.styles';
99

10+
interface OverflowState {
11+
hasOverflow: boolean;
12+
itemVisibility: Record<string, boolean>;
13+
groupVisibility: Record<string, OverflowGroupState>;
14+
}
15+
1016
/**
1117
* Overflow Props
1218
*/
@@ -24,21 +30,27 @@ export const Overflow = React.forwardRef((props: OverflowProps, ref) => {
2430

2531
const { children, minimumVisible, overflowAxis = 'horizontal', overflowDirection, padding } = props;
2632

27-
const [hasOverflow, setHasOverflow] = React.useState(false);
28-
const [itemVisibility, setItemVisibility] = React.useState<Record<string, boolean>>({});
29-
const [groupVisibility, setGroupVisibility] = React.useState<Record<string, OverflowGroupState>>({});
33+
const [overflowState, setOverflowState] = React.useState<OverflowState>({
34+
hasOverflow: false,
35+
itemVisibility: {},
36+
groupVisibility: {},
37+
});
3038

3139
// useOverflowContainer wraps this method in a useEventCallback.
32-
// TODO: Do we need a useEventCallback here too?
3340
const update: OnUpdateOverflow = data => {
34-
setHasOverflow(() => data.invisibleItems.length > 0);
35-
setItemVisibility(() => {
36-
const newState: Record<string, boolean> = {};
37-
data.visibleItems.forEach(x => (newState[x.id] = true));
38-
data.invisibleItems.forEach(x => (newState[x.id] = false));
39-
return newState;
41+
const { visibleItems, invisibleItems, groupVisibility } = data;
42+
43+
const itemVisibility: Record<string, boolean> = {};
44+
visibleItems.forEach(x => (itemVisibility[x.id] = true));
45+
invisibleItems.forEach(x => (itemVisibility[x.id] = false));
46+
47+
setOverflowState(() => {
48+
return {
49+
hasOverflow: data.invisibleItems.length > 0,
50+
itemVisibility,
51+
groupVisibility,
52+
};
4053
});
41-
setGroupVisibility(data.groupVisibility);
4254
};
4355

4456
const { containerRef, registerItem, updateOverflow, registerOverflowMenu } = useOverflowContainer(update, {
@@ -57,9 +69,9 @@ export const Overflow = React.forwardRef((props: OverflowProps, ref) => {
5769
return (
5870
<OverflowContext.Provider
5971
value={{
60-
itemVisibility,
61-
groupVisibility,
62-
hasOverflow,
72+
itemVisibility: overflowState.itemVisibility,
73+
groupVisibility: overflowState.groupVisibility,
74+
hasOverflow: overflowState.hasOverflow,
6375
registerItem,
6476
updateOverflow,
6577
registerOverflowMenu,

0 commit comments

Comments
 (0)