@samitouri / QOS-React-2 / commits / 2e5e88e127

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,