cbutler
cbutler

Reputation: 1505

react-custom-scrollbars jumps to top on any action

I am using react-custom-scrollbars in a react web app because I need to have two independant scroll bars, one for the main panel and one for the drawer panel. My issue is that the content in the main panel is dynamic and whenever I take some action in the main panel that changes state the scroll bar jumps to the top of the panel again.

UPDATE:

I believe I need to list for onUpdate and handle the scroll position there. If it has changed then update if not do not move the position?

In code, I have a HOC call withScrollbar as follows:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Scrollbars } from 'react-custom-scrollbars';

import { colors } from '../../theme/vars';
import { themes } from '../../types';



// This is a Higher Order Component (HOC) used to
// provide a scroll bar to other components

export default (ChildComponent, styling) => {
  class ComposedComponent extends Component {

    state = {
      // position: ??
    };

    handleUpdate = () => {
      //update position
      //this.scrollbar.scrollToBottom();
    };
    render() {
      return (

        <AutoSizer>
          {
            ({ width, height }) => (
              <Scrollbars
                style={{ width, height, backgroundColor: colors.WHITE, overflow: 'hidden', ...styling }}
                onUpdate={() => this.handleUpdate()}
                renderThumbVertical={props => <Thumb {...props} />}
                autoHide
                autoHideTimeout={1000}
                autoHideDuration={200}
              >
                <ChildComponent {...this.props} />
              </Scrollbars>
            )
          }
        </AutoSizer>
      );
    }
  }

  return ComposedComponent;
};


const Thumb = styled.div`
  background-color: ${props =>
    props.theme.theme === themes.LIGHT ? colors.BLACK : colors.WHITE};
  border-radius: 4px;
`;

in my MainView component I just wrap the export like this:

export default withScrollbar(LanguageProvider(connect(mapStateToProps, null)(MainView)));

I have read a few similar issues on this like this one: How to set initial scrollTop value to and this one scrollTo event but I cannot figure out how to implement in my case. Any tips or suggestions are greatly appreciated.

Upvotes: 1

Views: 7096

Answers (2)

NicoleMoore
NicoleMoore

Reputation: 335

I tried the above solution and it didn't seem to work for me.

However what did work was making sure that my child components inside Scrollbars were wrapped in a div with a height of 100%:

<Scrollbars>
  <div style={{ height: '100%' }}>
    <ChildComponent />
    <ChildComponent />
  </div>
</Scrollbars>

Upvotes: 0

cbutler
cbutler

Reputation: 1505

So I found a way to get this to work and it feels like a complete hack but I'm posting in hopes it might help someone else.

import React, { Component  } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Scrollbars } from 'react-custom-scrollbars';

import { colors } from '../../theme/vars';
import { themes } from '../../types';



// This is a Higher Order Component (HOC) used to
// provide a scroll bar to other components

export default (ChildComponent, styling) => {
  class ComposedComponent extends Component {

    state = {
      stateScrollTop: 0,
    };

    onScrollStart = () => {
      if (this.props.childRef) { // need to pass in a ref from the child component
        const { scrollTop } = this.props.childRef.current.getValues();
        const deltaY = Math.abs(scrollTop - this.state.stateScrollTop);
        if (deltaY > 100) { // 100 is arbitrary. It should not be a high value...
          this.props.childRef.current.scrollTop(this.state.stateScrollTop);
        }
      }
    };

    handleUpdate = () => {
      if (this.props.childRef) {
        const { scrollTop } = this.props.childRef.current.getValues();
        this.setState({ stateScrollTop: scrollTop });
      }
    };

    render() {
      return (
        <AutoSizer>
          {
            ({ width, height }) => (
              <Scrollbars
                ref={this.props.childRef}
                style={{ width, height, backgroundColor: colors.WHITE, overflow: 'hidden', ...styling }}
                onScrollStart={e => this.onScrollStart(e)}
                onUpdate={e => this.handleUpdate(e)}
                renderThumbVertical={props => <Thumb {...props} />}
                autoHide
                autoHideTimeout={1000}
                autoHideDuration={200}
              >
                <ChildComponent {...this.props} />
              </Scrollbars>
            )
          }
        </AutoSizer>
      );
    }
  }

  return ComposedComponent;
};


const Thumb = styled.div`
  background-color: ${props =>
    props.theme.theme === themes.LIGHT ? colors.BLACK : colors.WHITE};
  border-radius: 4px;
`;

I use this HOC like this:

  1. create a ref for the component you want to use it with
  2. pass the ref to the component that will use the HOC:
class SomeChildComponent extends Component {
...
viewRef = React.createRef();
...
render() {
  return ( <MainView childRef={this.viewRef} />)
}
  1. import and wrap the component
import withScrollbar from '../../hoc/withScrollbar';
...
export default withScrollbar(MainView);

Upvotes: 1

Related Questions