CustomScrollbar: Invoke setScrollTop callback only after scrolling finishes (#34263)

* Invoke setScrollTop callback only after scrolling finishes

When the state is updated while scroll events are being dispatched (like in QueryGroup) it may cause resetting the scroll position to the first emitted event because setting the scroll happens only after render (useEffect).

* Memoize onScrollStop callback
This commit is contained in:
Piotr Jamróz
2021-05-24 10:34:37 +02:00
committed by GitHub
parent 0c2bb9562a
commit 8d05df83ed
4 changed files with 26 additions and 13 deletions
@@ -2,10 +2,12 @@ import React, { FC, useCallback, useEffect, useRef } from 'react';
import { isNil } from 'lodash';
import classNames from 'classnames';
import { css } from '@emotion/css';
import Scrollbars from 'react-custom-scrollbars';
import Scrollbars, { positionValues } from 'react-custom-scrollbars';
import { useStyles2 } from '../../themes';
import { GrafanaTheme2 } from '@grafana/data';
export type ScrollbarPosition = positionValues;
interface Props {
className?: string;
autoHide?: boolean;
@@ -15,7 +17,7 @@ interface Props {
hideHorizontalTrack?: boolean;
hideVerticalTrack?: boolean;
scrollTop?: number;
setScrollTop?: (event: any) => void;
setScrollTop?: (position: ScrollbarPosition) => void;
autoHeightMin?: number | string;
updateAfterMountMs?: number;
}
@@ -101,11 +103,15 @@ export const CustomScrollbar: FC<Props> = ({
return <div {...passedProps} className="scrollbar-view" />;
}, []);
const onScrollStop = useCallback(() => {
ref.current && setScrollTop && setScrollTop(ref.current.getValues());
}, [setScrollTop]);
return (
<Scrollbars
ref={ref}
className={classNames(styles.customScrollbar, className)}
onScroll={setScrollTop}
onScrollStop={onScrollStop}
autoHeight={true}
autoHide={autoHide}
autoHideTimeout={autoHideTimeout}
+1 -1
View File
@@ -6,7 +6,7 @@ export { Tooltip, PopoverContent } from './Tooltip/Tooltip';
export { PopoverController } from './Tooltip/PopoverController';
export { Popover } from './Tooltip/Popover';
export { Portal } from './Portal/Portal';
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
export { CustomScrollbar, ScrollbarPosition } from './CustomScrollbar/CustomScrollbar';
export { TabbedContainer, TabConfig } from './TabbedContainer/TabbedContainer';
export { ClipboardButton } from './ClipboardButton/ClipboardButton';
@@ -1,11 +1,11 @@
import $ from 'jquery';
import React, { MouseEvent, PureComponent } from 'react';
import React, { PureComponent } from 'react';
import { css } from 'emotion';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import { locationService } from '@grafana/runtime';
import { selectors } from '@grafana/e2e-selectors';
import { CustomScrollbar, stylesFactory, Themeable2, withTheme2 } from '@grafana/ui';
import { CustomScrollbar, ScrollbarPosition, stylesFactory, Themeable2, withTheme2 } from '@grafana/ui';
import { createErrorNotification } from 'app/core/copy/appNotification';
import { Branding } from 'app/core/components/Branding/Branding';
@@ -241,9 +241,8 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
$('body').toggleClass('panel-in-fullscreen', isFullscreen);
}
setScrollTop = (e: MouseEvent<HTMLElement>): void => {
const target = e.target as HTMLElement;
this.setState({ scrollTop: target.scrollTop, updateScrollTop: undefined });
setScrollTop = ({ scrollTop }: ScrollbarPosition): void => {
this.setState({ scrollTop, updateScrollTop: undefined });
};
onAddPanel = () => {
@@ -1,7 +1,16 @@
// Libraries
import React, { PureComponent } from 'react';
// Components
import { Button, CustomScrollbar, HorizontalGroup, Icon, Modal, stylesFactory, Tooltip } from '@grafana/ui';
import {
Button,
CustomScrollbar,
HorizontalGroup,
Icon,
Modal,
ScrollbarPosition,
stylesFactory,
Tooltip,
} from '@grafana/ui';
import { getDataSourceSrv, DataSourcePicker } from '@grafana/runtime';
import { QueryEditorRows } from './QueryEditorRows';
// Services
@@ -275,9 +284,8 @@ export class QueryGroup extends PureComponent<Props, State> {
this.onScrollBottom();
};
setScrollTop = (event: React.MouseEvent<HTMLElement>) => {
const target = event.target as HTMLElement;
this.setState({ scrollTop: target.scrollTop });
setScrollTop = ({ scrollTop }: ScrollbarPosition) => {
this.setState({ scrollTop: scrollTop });
};
onQueriesChange = (queries: DataQuery[]) => {