Cleaned up is-overflowing hook and dependencies
Brian Vaughn committed
Apr 7, 2019 at 10:34 UTC
2e5e88e127f5bac959aef7c0536b9d0cecd746ab
2 files changed
+52
-46
src/devtools/views/Components/OwnersStack.js
+19
-46
@@ -7,12 +7,12 @@ import React, {
7
createRef,
8
forwardRef,
9
} from 'react';
10
-import throttle from 'lodash.throttle';
10
import classNames from 'classnames';
11
import Button from '../Button';
12
import ButtonIcon from '../ButtonIcon';
13
import { TreeContext } from './TreeContext';
14
import { StoreContext } from '../context';
15
+import { useIsOverflowing } from '../hooks';
16
17
import type { Element } from './types';
18
@@ -27,6 +27,7 @@ function ElementsDropdown({
27
children,
28
}: ElementsDropdownProps) {
29
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
30
+
31
const handleClick = useCallback(() => {
32
setIsDropdownVisible(!isDropdownVisible);
33
}, [isDropdownVisible, setIsDropdownVisible]);
@@ -102,11 +103,11 @@ export default function OwnerStack() {
103
const { ownerStack, ownerStackIndex, resetOwnerStack } = useContext(
104
TreeContext
105
);
105
- const [isElementsBarOverflowing, setIsElementsBarOverflowing] = useState(
106
- false
107
- );
106
+
107
const [elementsTotalWidth, setElementsTotalWidth] = useState(0);
108
const elementsBarRef = createRef<HTMLDivElement | null>();
109
+ const isOverflowing = useIsOverflowing(elementsBarRef, elementsTotalWidth);
110
+
111
const elements = ownerStack.map((id, index) => (
112
<ElementView key={id} id={id} index={index} />
113
));
@@ -115,22 +116,20 @@ export default function OwnerStack() {
116
if (elementsBarRef.current === null) {
117
return () => {};
118
}
118
- const elements = Array.from(elementsBarRef.current.children);
119
- const elementsTotalWidth = elements.reduce((acc, el) => {
120
- const { offsetWidth } = el;
121
- const marginRight = parseInt(getComputedStyle(el).marginRight, 10);
122
- return acc + (offsetWidth + marginRight);
123
- }, 0);
119
125
- setElementsTotalWidth(elementsTotalWidth);
126
- // eslint-disable-next-line react-hooks/exhaustive-deps
127
- }, [ownerStackIndex, elementsBarRef]);
120
+ let elementsTotalWidth = 0;
121
+ for (let i = 0; i < ownerStack.length; i++) {
122
+ const element = elementsBarRef.current.children[i];
123
+ const computedStyle = getComputedStyle(element);
124
129
- useElementsBarOverflowing(
130
- elementsBarRef,
131
- elementsTotalWidth,
132
- setIsElementsBarOverflowing
133
- );
125
+ elementsTotalWidth +=
126
+ element.offsetWidth +
127
+ parseInt(computedStyle.marginLeft, 10) +
128
+ parseInt(computedStyle.marginRight, 10);
129
+ }
130
+
131
+ setElementsTotalWidth(elementsTotalWidth);
132
+ }, [elementsBarRef, ownerStack.length]);
133
134
return (
135
<div className={styles.OwnerStack}>
@@ -141,7 +140,7 @@ export default function OwnerStack() {
140
>
141
<ButtonIcon type="close" />
142
</Button>
144
- {isElementsBarOverflowing && (
143
+ {isOverflowing && (
144
<ElementsDropdown selectedElementIndex={ownerStackIndex}>
145
{elements}
146
</ElementsDropdown>
@@ -149,35 +148,9 @@ export default function OwnerStack() {
148
<div className={styles.VRule} />
149
<ElementsBar
150
elements={elements}
152
- showSelectedOnly={isElementsBarOverflowing}
151
+ showSelectedOnly={isOverflowing}
152
ref={elementsBarRef}
153
/>
154
</div>
155
);
156
}
158
-
159
-function useElementsBarOverflowing(
160
- elementsBarRef: Object,
161
- elementsTotalWidth: number,
162
- callback: Function
163
-) {
164
- useLayoutEffect(() => {
165
- const handleResize = throttle(() => {
166
- let isElementsBarOverflowing = false;
167
- if (elementsBarRef.current !== null) {
168
- const elementsBarWidth = elementsBarRef.current.clientWidth;
169
- isElementsBarOverflowing = elementsBarWidth <= elementsTotalWidth;
170
- }
171
- callback(isElementsBarOverflowing);
172
- }, 100);
173
-
174
- handleResize();
175
-
176
- // It's important to listen to the ownerDocument.defaultView to support the browser extension.
177
- // Here we use portals to render individual tabs (e.g. Profiler),
178
- // and the root document might belong to a different window.
179
- const ownerWindow = elementsBarRef.current.ownerDocument.defaultView;
180
- ownerWindow.addEventListener('resize', handleResize);
181
- return () => ownerWindow.removeEventListener('resize', handleResize);
182
- }, [elementsBarRef, elementsTotalWidth, callback]);
183
-}
src/devtools/views/hooks.js
+33
@@ -1,7 +1,40 @@
1
// @flow
2
3
+import throttle from 'lodash.throttle';
4
import { useCallback, useEffect, useLayoutEffect, useState } from 'react';
5
6
+export function useIsOverflowing(
7
+ containerRef: { current: HTMLDivElement | null },
8
+ totalChildWidth: number
9
+): boolean {
10
+ const [isOverflowing, setIsOverflowing] = useState<boolean>(false);
11
+
12
+ // It's important to use a layout effect, so that we avoid showing a flash of overflowed content.
13
+ useLayoutEffect(() => {
14
+ if (containerRef.current === null) {
15
+ return () => {};
16
+ }
17
+
18
+ const container = ((containerRef.current: any): HTMLDivElement);
19
+
20
+ const handleResize = throttle(
21
+ () => setIsOverflowing(container.clientWidth <= totalChildWidth),
22
+ 100
23
+ );
24
+
25
+ handleResize();
26
+
27
+ // It's important to listen to the ownerDocument.defaultView to support the browser extension.
28
+ // Here we use portals to render individual tabs (e.g. Profiler),
29
+ // and the root document might belong to a different window.
30
+ const ownerWindow = container.ownerDocument.defaultView;
31
+ ownerWindow.addEventListener('resize', handleResize);
32
+ return () => ownerWindow.removeEventListener('resize', handleResize);
33
+ }, [containerRef, totalChildWidth]);
34
+
35
+ return isOverflowing;
36
+}
37
+
38
// Forked from https://usehooks.com/useLocalStorage/
39
export function useLocalStorage<T>(
40
key: string,