From 8cd38a6b49d1c0b264a968cc407328d0fe3109d3 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Wed, 31 Mar 2021 03:42:59 -0700 Subject: [PATCH] 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 --- desktop/flipper-plugin/package.json | 1 + .../flipper-plugin/src/ui/DataFormatter.tsx | 33 +++--- .../src/ui/datatable/DataSourceRenderer.tsx | 101 ++++++++++++------ desktop/yarn.lock | 2 +- 4 files changed, 92 insertions(+), 45 deletions(-) diff --git a/desktop/flipper-plugin/package.json b/desktop/flipper-plugin/package.json index 9980ba2d3..6ced31312 100644 --- a/desktop/flipper-plugin/package.json +++ b/desktop/flipper-plugin/package.json @@ -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", diff --git a/desktop/flipper-plugin/src/ui/DataFormatter.tsx b/desktop/flipper-plugin/src/ui/DataFormatter.tsx index bf89d0773..62da1686e 100644 --- a/desktop/flipper-plugin/src/ui/DataFormatter.tsx +++ b/desktop/flipper-plugin/src/ui/DataFormatter.tsx @@ -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': { - marginRight: 4, - }, -}); +} + +const truncateButtonStyle = { + color: theme.textColorPrimary, + marginLeft: 4, +}; diff --git a/desktop/flipper-plugin/src/ui/datatable/DataSourceRenderer.tsx b/desktop/flipper-plugin/src/ui/datatable/DataSourceRenderer.tsx index 705ffafb9..93e3c4b42 100644 --- a/desktop/flipper-plugin/src/ui/datatable/DataSourceRenderer.tsx +++ b/desktop/flipper-plugin/src/ui/datatable/DataSourceRenderer.tsx @@ -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: ( // 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); const virtualizer = useVirtual({ @@ -111,6 +113,11 @@ export const DataSourceRenderer: ( virtualizerRef.current = virtualizer; } + const redraw = useCallback(() => { + forceHeightRecalculation.current++; + setForceUpdate((x) => x + 1); + }, []); + useEffect( function subscribeToDataSource() { const forceUpdate = () => { @@ -261,40 +268,64 @@ export const DataSourceRenderer: ( 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 ( - - {virtualizer.virtualItems.length === 0 - ? emptyRenderer?.(dataSource) - : null} - - {virtualizer.virtualItems.map((virtualRow) => { - const value = dataSource.view.get(virtualRow.index); - // the position properties always change, so they are not part of the TableRow to avoid invalidating the memoized render always. - // Also all row containers are renderd as part of same component to have 'less react' framework code in between*/} - return ( -
- {itemRenderer(value, virtualRow.index, context)} -
- ); - })} -
-
+ + + {virtualizer.virtualItems.length === 0 + ? emptyRenderer?.(dataSource) + : null} + + {virtualizer.virtualItems.map((virtualRow) => { + const value = dataSource.view.get(virtualRow.index); + // the position properties always change, so they are not part of the TableRow to avoid invalidating the memoized render always. + // Also all row containers are renderd as part of same component to have 'less react' framework code in between*/} + return ( +
+ {itemRenderer(value, virtualRow.index, context)} +
+ ); + })} +
+
+
); }) as any; @@ -310,3 +341,9 @@ const TableWindow = styled.div<{height: number}>(({height}) => ({ position: 'relative', width: '100%', })); + +export const RedrawContext = createContext void)>(undefined); + +export function useTableRedraw() { + return useContext(RedrawContext); +} diff --git a/desktop/yarn.lock b/desktop/yarn.lock index c5f573a07..90f64b44b 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -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==