Skip to content

Commit ef8c5c7

Browse files
committed
use resize observer library
1 parent 5e9e006 commit ef8c5c7

File tree

6 files changed

+56
-111
lines changed

6 files changed

+56
-111
lines changed

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,28 +65,29 @@
6565
"@testing-library/react-hooks": "^8.0.1",
6666
"@types/react-window": "^1.8.5",
6767
"cross-env": "^7.0.3",
68-
"eslint-config-standard-react": "^13.0.0",
68+
"eslint": "8.50.0",
6969
"eslint-config-standard": "^17.1.0",
70+
"eslint-config-standard-react": "^13.0.0",
7071
"eslint-plugin-flowtype": "^8.0.3",
7172
"eslint-plugin-import": "^2.28.1",
7273
"eslint-plugin-node": "^11.1.0",
7374
"eslint-plugin-prettier": "^5.0.0",
7475
"eslint-plugin-promise": "^6.1.1",
7576
"eslint-plugin-react": "^7.33.2",
76-
"eslint": "8.50.0",
7777
"gh-pages": "^6.0.0",
7878
"postcss": "^8.4.30",
7979
"prettier": "^3.0.3",
8080
"react": "^18.2.0",
81+
"rollup": "^3.29.3",
8182
"rollup-plugin-analyzer": "^4.0.0",
8283
"rollup-plugin-bundle-size": "^1.0.3",
8384
"rollup-plugin-peer-deps-external": "^2.2.4",
8485
"rollup-plugin-postcss": "^4.0.2",
8586
"rollup-plugin-visualizer": "^5.9.2",
86-
"rollup": "^3.29.3",
8787
"typescript": "^5.2.2"
8888
},
8989
"dependencies": {
90+
"react-resize-detector": "^9.1.0",
9091
"react-window": "^1.8.9"
9192
},
9293
"volta": {

src/AutoSizer.tsx

Lines changed: 13 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
1+
import React, { useContext, useMemo } from "react";
2+
import { useResizeDetector } from "react-resize-detector";
23
import { TableContext } from "./TableContext";
34
import { DEFAULT_FOOTER_HEIGHT, DEFAULT_HEADER_HEIGHT, DEFAULT_ROW_HEIGHT } from "./constants";
45
import { findFooterByUuid, findHeaderByUuid } from "./util";
@@ -90,7 +91,7 @@ const calculateHeight = (
9091
const borderOffset = !!table ? table.offsetHeight - table.clientHeight : 0;
9192

9293
// if there are rows, let's do the calculation
93-
if (!!nodes.length) {
94+
if (nodes.length) {
9495
if (rowHeight > 0) {
9596
return headerOffset + nodes.length * rowHeight + footerOffset + borderOffset;
9697
}
@@ -116,8 +117,8 @@ const calculateHeight = (
116117
/**
117118
* This is a skinny AutoSizer based on react-virtualized-auto-sizer.
118119
* This removes the `bailout` functionality in order to allow the Table
119-
* to generate its own height. This also ignores a resize if the
120-
* dimensions of the window did not actually change (one less render).
120+
* to generate its own height. This uses ResizeObserver to observe the
121+
* container when it changes in order to provide the correct height
121122
*/
122123
const AutoSizer = ({
123124
numRows,
@@ -131,22 +132,22 @@ const AutoSizer = ({
131132
children
132133
}: AutoSizerProps) => {
133134
// hooks
134-
const resizeRef = useRef(0);
135-
const ref = useRef<HTMLDivElement>(null);
135+
const {
136+
ref,
137+
width: containerWidth,
138+
height: containerHeight
139+
} = useResizeDetector<HTMLDivElement>();
136140
const { uuid, columns, footerComponent } = useContext(TableContext);
137-
const [dimensions, setDimensions] = useState({ containerHeight: 0, containerWidth: 0 });
138141

139142
// variables
140-
const { containerHeight, containerWidth } = dimensions;
141143
const hasFooter = useMemo(
142144
() => !!footerComponent || !!columns.find(c => !!c.footer),
143145
[columns, footerComponent]
144146
);
145-
const fixedTableSize = !!tableHeight && tableHeight > 0 && !!tableWidth && tableWidth > 0;
146147

147148
// calculate the computed height
148149
const computedHeight = useMemo(() => {
149-
if (!!tableHeight && tableHeight > 0) {
150+
if (tableHeight && tableHeight > 0) {
150151
return tableHeight;
151152
}
152153

@@ -163,64 +164,14 @@ const AutoSizer = ({
163164
// calculate the actual height of the table
164165
const height = findCorrectHeight({
165166
computedHeight,
166-
containerHeight,
167+
containerHeight: containerHeight || 0,
167168
tableHeight: tableHeight || 0,
168169
minHeight: minTableHeight || 0,
169170
maxHeight: maxTableHeight || 0
170171
});
171172

172173
// get actual width
173-
const width = !!tableWidth && tableWidth > 0 ? tableWidth : containerWidth;
174-
175-
// functions
176-
const calculateDimensions = useCallback(() => {
177-
// base cases
178-
if (!ref.current?.parentElement || fixedTableSize) {
179-
return;
180-
}
181-
182-
// get style
183-
const parent = ref.current.parentElement;
184-
const style = window.getComputedStyle(parent);
185-
const paddingLeft = parseInt(style.paddingLeft) || 0;
186-
const paddingRight = parseInt(style.paddingRight) || 0;
187-
const paddingTop = parseInt(style.paddingTop) || 0;
188-
const paddingBottom = parseInt(style.paddingBottom) || 0;
189-
190-
// find new dimensions
191-
const newHeight = Math.max((parent.offsetHeight || 0) - paddingTop - paddingBottom, 0);
192-
const newWidth = Math.max((parent.offsetWidth || 0) - paddingLeft - paddingRight, 0);
193-
194-
// update state
195-
setDimensions(prev => {
196-
const { containerHeight: oldHeight, containerWidth: oldWidth } = prev;
197-
if (oldHeight !== newHeight || oldWidth !== newWidth) {
198-
return { containerHeight: newHeight, containerWidth: newWidth };
199-
}
200-
201-
return prev;
202-
});
203-
}, [fixedTableSize]);
204-
205-
const onResize = useCallback(() => {
206-
window.clearTimeout(resizeRef.current);
207-
resizeRef.current = window.setTimeout(calculateDimensions, 40);
208-
}, [calculateDimensions]);
209-
210-
// effects
211-
// on mount, calculate the dimensions
212-
useEffect(() => calculateDimensions(), [calculateDimensions]);
213-
214-
// on resize, we have to re-calculate the dimensions
215-
useEffect(() => {
216-
window.removeEventListener("resize", onResize);
217-
window.addEventListener("resize", onResize);
218-
const m = resizeRef.current;
219-
return () => {
220-
window.clearTimeout(m);
221-
window.removeEventListener("resize", onResize);
222-
};
223-
}, [onResize]);
174+
const width = tableWidth && tableWidth > 0 ? tableWidth : containerWidth || 0;
224175

225176
return <div ref={ref}>{height || width ? children({ height, width }) : null}</div>;
226177
};

src/Row.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import React, { SVGProps, useCallback, useContext, useLayoutEffect, useRef } fro
22
import { CacheFunction, ColumnProps, RowRenderProps, SubComponentProps } from "../index";
33
//@ts-ignore TS2307
44
import Minus from "./svg/minus-circle.svg";
5+
import { ListChildComponentProps } from "react-window";
6+
import { TableContext } from "./TableContext";
57
//@ts-ignore TS2307
68
import Plus from "./svg/plus-circle.svg";
7-
import { TableContext } from "./TableContext";
8-
import { ListChildComponentProps } from "react-window";
99
import { cx } from "./util";
1010

1111
interface TableCellProps<T> {
@@ -197,7 +197,7 @@ function Row<T>({
197197
const containerHeight = !rowHeight ? undefined : isExpanded && SubComponent ? rowHeight : "100%";
198198

199199
// sub component props
200-
const subProps = { row, index, isExpanded, clearSizeCache };
200+
const subProps: SubComponentProps<T> = { row, index, isExpanded, clearSizeCache };
201201

202202
// row styling
203203
const borderBottom = borders ? undefined : "none";
@@ -221,7 +221,7 @@ function Row<T>({
221221
if (height !== calculateHeight(rowRef.current, index)) {
222222
clearSizeCache(index);
223223
}
224-
}, [rowRef, index, height, calculateHeight, clearSizeCache, pixelWidths]);
224+
}, [index, height, calculateHeight, clearSizeCache, pixelWidths]);
225225

226226
// effects
227227
// on expansion, clear the cache
@@ -234,7 +234,7 @@ function Row<T>({
234234
}
235235

236236
expandedCalledRef.current = false;
237-
}, [isExpanded, expandedCalledRef, resetHeight, index, clearSizeCache]);
237+
}, [isExpanded, resetHeight, index, clearSizeCache]);
238238

239239
return (
240240
<div

src/Table.tsx

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ const ListComponent = forwardRef(function (
5656
) {
5757
// hooks
5858
const timeoutRef = useRef(0);
59-
const prevRef = useRef(width);
6059
const cacheRef = useRef<any>({});
6160
const listRef = useRef<any>(null);
61+
const prevWidthRef = useRef(width);
6262
const treeRef = useRef(new NumberTree());
6363
const tableRef = useRef<HTMLDivElement>(null);
6464
const { dispatch, uuid, columns, minColumnWidth, fixedWidth, remainingCols, pixelWidths } =
@@ -80,28 +80,25 @@ const ListComponent = forwardRef(function (
8080
[itemKey]
8181
);
8282

83-
const clearSizeCache = useCallback(
84-
(dataIndex: number, forceUpdate = false) => {
85-
if (!listRef.current) {
86-
return;
87-
}
83+
const clearSizeCache = useCallback((dataIndex: number, forceUpdate = false) => {
84+
if (!listRef.current) {
85+
return;
86+
}
8887

89-
window.clearTimeout(timeoutRef.current);
90-
if (forceUpdate) {
91-
treeRef.current.clearFromIndex(dataIndex);
92-
listRef.current.resetAfterIndex(dataIndex + 1);
93-
return;
94-
}
88+
window.clearTimeout(timeoutRef.current);
89+
if (forceUpdate) {
90+
treeRef.current.clearFromIndex(dataIndex);
91+
listRef.current.resetAfterIndex(dataIndex + 1);
92+
return;
93+
}
9594

96-
timeoutRef.current = window.setTimeout(() => {
97-
const node = tableRef.current?.children[1].children[0] as HTMLElement;
98-
const resetIndex = parseInt(node?.dataset.index || "0") + 1;
99-
treeRef.current.clearFromIndex(resetIndex);
100-
listRef.current.resetAfterIndex(resetIndex);
101-
}, 50);
102-
},
103-
[listRef, tableRef, timeoutRef, treeRef]
104-
);
95+
timeoutRef.current = window.setTimeout(() => {
96+
const node = tableRef.current?.children[1].children[0] as HTMLElement;
97+
const resetIndex = parseInt(node?.dataset.index || "0") + 1;
98+
treeRef.current.clearFromIndex(resetIndex);
99+
listRef.current.resetAfterIndex(resetIndex);
100+
}, 50);
101+
}, []);
105102

106103
const calculateHeight = useCallback(
107104
(queryParam: number | HTMLElement, optionalDataIndex: number | null = null) => {
@@ -114,7 +111,7 @@ const ListComponent = forwardRef(function (
114111
}
115112

116113
const arr = [...row.children].slice(rowHeight ? 1 : 0) as HTMLElement[];
117-
const res = (rowHeight || 0) + arr.reduce((pv, c) => pv + c.offsetHeight, 0);
114+
const res = arr.reduce((pv, c) => pv + c.offsetHeight, rowHeight || 0) || defaultSize;
118115

119116
// update the calculated height ref
120117
cacheRef.current[dataIndex] = res;
@@ -139,7 +136,7 @@ const ListComponent = forwardRef(function (
139136
const shouldUseRowWidth = useCallback(() => {
140137
const parentElement = tableRef.current?.parentElement || NO_NODE;
141138
setUseRowWidth(parentElement.scrollWidth <= parentElement.clientWidth);
142-
}, [tableRef]);
139+
}, []);
143140

144141
// effects
145142
/* initializers */
@@ -149,17 +146,6 @@ const ListComponent = forwardRef(function (
149146
setUseRowWidth(widths.scrollWidth <= widths.clientWidth);
150147
}, []);
151148

152-
// force clear cache to update header size
153-
useEffect(() => {
154-
clearSizeCache(-1, true);
155-
// figure out how to wait for scrollbar to appear
156-
// before recalculating. using 100ms heuristic
157-
setTimeout(() => {
158-
updatePixelWidths();
159-
shouldUseRowWidth();
160-
}, 100);
161-
}, []);
162-
163149
/* updates */
164150
// update pixel widths every time the width changes
165151
useLayoutEffect(() => updatePixelWidths(), [width]);
@@ -170,7 +156,7 @@ const ListComponent = forwardRef(function (
170156
// manually alter the height of each row if height is incorrect
171157
// to help with flicker on resize
172158
useLayoutEffect(() => {
173-
if (prevRef.current !== width) {
159+
if (prevWidthRef.current !== width) {
174160
treeRef.current.clearFromIndex(0);
175161
setTimeout(() => {
176162
if (!tableRef.current || !listRef.current) {
@@ -215,8 +201,8 @@ const ListComponent = forwardRef(function (
215201
}, 0);
216202
}
217203

218-
prevRef.current = width;
219-
}, [width, tableRef, listRef, calculateHeight]);
204+
prevWidthRef.current = width;
205+
}, [width, calculateHeight]);
220206

221207
// for the footer: set the rows in the context with the data.
222208
// this is useful for any aggregate calculations.
@@ -261,7 +247,7 @@ const ListComponent = forwardRef(function (
261247
}}
262248
itemSize={index => {
263249
if (!index) {
264-
if (!!headerHeight && headerHeight > 0) {
250+
if (headerHeight && headerHeight > 0) {
265251
return headerHeight;
266252
}
267253

@@ -340,13 +326,13 @@ const Table = forwardRef(function <T>(
340326

341327
// warn if a minHeight is set without a maxHeight
342328
let maxHeight = maxTableHeight;
343-
if (!!minTableHeight && minTableHeight > 0 && (!maxTableHeight || maxTableHeight <= 0)) {
329+
if (minTableHeight && minTableHeight > 0 && (!maxTableHeight || maxTableHeight <= 0)) {
344330
maxHeight = minTableHeight + 400;
345331
}
346332

347333
// handle warning
348334
useEffect(() => {
349-
if (!!minTableHeight && minTableHeight > 0 && (!maxTableHeight || maxTableHeight <= 0)) {
335+
if (minTableHeight && minTableHeight > 0 && (!maxTableHeight || maxTableHeight <= 0)) {
350336
console.warn(
351337
`maxTableHeight was either not present, or is <= 0, but you provided a minTableHeight of ${minTableHeight}px. As a result, the maxTableHeight will be set to ${
352338
minTableHeight + 400

src/TableContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ const TableContextProvider = ({ children, initialState }: ProviderProps) => {
156156
_stateOnMount.current = initialState;
157157
dispatch({ type: "refresh", initialState: refreshed });
158158
}
159-
}, [_stateOnMount, initialState]);
159+
}, [initialState]);
160160

161161
return <TableContext.Provider value={{ ...state, dispatch }}>{children}</TableContext.Provider>;
162162
};

yarn.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4220,6 +4220,13 @@ react-is@^16.13.1:
42204220
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
42214221
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
42224222

4223+
react-resize-detector@^9.1.0:
4224+
version "9.1.0"
4225+
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-9.1.0.tgz#45ea7176e57f1a568abd0e3eafcbfd3532fb284e"
4226+
integrity sha512-vGFbfkIZp4itJqR4yl+GhjrZHtdlQvou1r10ek0yZUMkizKbPdekKTpPb003IV3b8E5BJFThVG0oocjE3lNsug==
4227+
dependencies:
4228+
lodash "^4.17.21"
4229+
42234230
react-window@^1.8.9:
42244231
version "1.8.9"
42254232
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.9.tgz#24bc346be73d0468cdf91998aac94e32bc7fa6a8"

0 commit comments

Comments
 (0)