Fix redraw after resizing elements

Summary:
Fixed a longer standing issue where after a horizontal resize the rows wouldn't redraw until new data arrives (or the user scrolls), resulting in rendering artefacts.

Also introduced a hook to force a reflow of contents if the contents of a hook changes. It's a bit leaky abstraction, but does keep the virtualization performant if dynamic heights are used.

Reviewed By: passy

Differential Revision: D27395516

fbshipit-source-id: 1691af3ec64f1a476969a318553d83e22239997c
This commit is contained in:
Michel Weststrate
2021-03-31 03:42:59 -07:00
committed by Facebook GitHub Bot
parent b597da01e7
commit 8cd38a6b49
4 changed files with 92 additions and 45 deletions

View File

@@ -11,6 +11,7 @@
"dependencies": {
"@emotion/css": "^11.1.3",
"@emotion/react": "^11.1.5",
"@reach/observe-rect": "^1.2.0",
"immer": "^9.0.0",
"lodash": "^4.17.21",
"react-element-to-jsx-string": "^14.3.2",

View File

@@ -15,10 +15,11 @@ import {
import {Button, Typography} from 'antd';
import {pad} from 'lodash';
import React, {createElement, Fragment, isValidElement, useState} from 'react';
import styled from '@emotion/styled';
import {tryGetFlipperLibImplementation} from '../plugin/FlipperLib';
import {safeStringify} from '../utils/safeStringify';
import {urlRegex} from '../utils/urlRegex';
import {useTableRedraw} from './datatable/DataSourceRenderer';
import {theme} from './theme';
/**
* A Formatter is used to render an arbitrarily value to React. If a formatter returns 'undefined'
@@ -136,7 +137,7 @@ export const DataFormatter = {
},
};
const TruncateHelper = styled(function TruncateHelper({
function TruncateHelper({
value,
maxLength,
}: {
@@ -144,29 +145,37 @@ const TruncateHelper = styled(function TruncateHelper({
maxLength: number;
}) {
const [collapsed, setCollapsed] = useState(true);
const redrawRow = useTableRedraw();
return (
<>
{collapsed ? value.substr(0, maxLength) : value}
<Button
onClick={() => setCollapsed((c) => !c)}
onClick={() => {
setCollapsed((c) => !c);
redrawRow?.();
}}
size="small"
type="text"
style={truncateButtonStyle}
icon={collapsed ? <CaretRightOutlined /> : <CaretUpOutlined />}>
{`(and ${value.length - maxLength} more...)`}
</Button>
<Button
icon={<CopyOutlined />}
onClick={() =>
tryGetFlipperLibImplementation()?.writeTextToClipboard(value)
}
onClick={() => {
tryGetFlipperLibImplementation()?.writeTextToClipboard(value);
}}
size="small"
type="text">
type="text"
style={truncateButtonStyle}>
Copy
</Button>
</>
);
})({
'& button': {
marginRight: 4,
},
});
}
const truncateButtonStyle = {
color: theme.textColorPrimary,
marginLeft: 4,
};

View File

@@ -15,10 +15,13 @@ import React, {
useState,
useLayoutEffect,
MutableRefObject,
useContext,
createContext,
} from 'react';
import {DataSource} from '../../state/DataSource';
import {useVirtual} from 'react-virtual';
import styled from '@emotion/styled';
import observeRect from '@reach/observe-rect';
// how fast we update if updates are low-prio (e.g. out of window and not super significant)
const LOW_PRIO_UPDATE = 1000; //ms
@@ -92,9 +95,8 @@ export const DataSourceRenderer: <T extends object, C>(
// render scheduling
const renderPending = useRef(UpdatePrio.NONE);
const lastRender = useRef(Date.now());
const setForceUpdate = useState(0)[1];
const [, setForceUpdate] = useState(0);
const forceHeightRecalculation = useRef(0);
const parentRef = React.useRef<null | HTMLDivElement>(null);
const virtualizer = useVirtual({
@@ -111,6 +113,11 @@ export const DataSourceRenderer: <T extends object, C>(
virtualizerRef.current = virtualizer;
}
const redraw = useCallback(() => {
forceHeightRecalculation.current++;
setForceUpdate((x) => x + 1);
}, []);
useEffect(
function subscribeToDataSource() {
const forceUpdate = () => {
@@ -261,10 +268,33 @@ export const DataSourceRenderer: <T extends object, C>(
lastRender.current = Date.now();
});
/**
* Observer parent height
*/
useEffect(
function redrawOnResize() {
if (!parentRef.current) {
return;
}
let lastWidth = 0;
const observer = observeRect(parentRef.current, (rect) => {
if (lastWidth !== rect.width) {
lastWidth = rect.width;
redraw();
}
});
observer.observe();
return () => observer.unobserve();
},
[redraw],
);
/**
* Rendering
*/
return (
<RedrawContext.Provider value={redraw}>
<TableContainer onScroll={onScroll} ref={parentRef}>
{virtualizer.virtualItems.length === 0
? emptyRenderer?.(dataSource)
@@ -295,6 +325,7 @@ export const DataSourceRenderer: <T extends object, C>(
})}
</TableWindow>
</TableContainer>
</RedrawContext.Provider>
);
}) as any;
@@ -310,3 +341,9 @@ const TableWindow = styled.div<{height: number}>(({height}) => ({
position: 'relative',
width: '100%',
}));
export const RedrawContext = createContext<undefined | (() => void)>(undefined);
export function useTableRedraw() {
return useContext(RedrawContext);
}

View File

@@ -2150,7 +2150,7 @@
resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493"
integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw==
"@reach/observe-rect@^1.1.0":
"@reach/observe-rect@^1.1.0", "@reach/observe-rect@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==