React, Using Refs to scrollIntoView() doen't work on componentDidUpdate()

Viewed 20705

I'm using Redux in my app, inside a Component I want to scroll to an specific div tag when a change in the store happens. I have the Redux part working so it triggers the componentDidUpdate() method (I routed to this compoennt view already). The problem as far as I can tell, is that the method scrollIntoView() doesn't work properly cos componentDidUpdate() has a default behavior that scrolls to the top overwriting the scrollIntoView(). To work-around it I wrapped the function calling scrollIntoView() in a setTimeout to ensure that happens afeterwards. What I would like to do is to call a preventDefault() or any other more elegant solution but I can't find where to get the event triggering the 'scrollTop' I looked through the Doc here: https://facebook.github.io/react/docs/react-component.html#componentdidupdate and the params passed in this function are componentDidUpdate(prevProps, prevState) ,since there is no event I don't know how to call preventDefault()

I've followd this Docs: https://facebook.github.io/react/docs/refs-and-the-dom.html And tried different approaches people suggested here: How can I scroll a div to be visible in ReactJS?

Nothing worked though Here is my code if anyone has any tip for me, thanks

class PhotoContainer extends React.Component {

  componentDidUpdate(){
    setTimeout(() => {
     this.focusDiv();
    }, 500);

  }
  focusDiv(){
    var scrolling = this.theDiv;
    scrolling.scrollIntoView();

  }

  render() {
    const totalList = [];
    for(let i = 0; i < 300; i += 1) {
        totalList.push(
            <div key={i}>{`hello ${i}`}</div>
        );
    }

  return (
      <div >
          {totalList}
          <div ref={(el) => this.theDiv = el}>this is the div I'm trying to scroll to</div>
      </div>
  )

}; }

2 Answers

Ok it's been a while but I got it working in another project without the setTimeOut function so I wanted to answer this question. Since Redux pass the new updates through props, I used the componentWillRecieveProps() method instead of componentDidUpdate() , this allowes you a better control over the updated properties and works as expected with the scrollIntoView() function.

class PhotoContainer extends React.Component {

  componentWillReceiveProps(newProps) {
    if (
      this.props.navigation.sectionSelected !==
        newProps.navigation.sectionSelected &&
      newProps.navigation.sectionSelected !== ""
    ) {
      this.focusDiv(newProps.navigation.sectionSelected);
    }
  }

  focusDiv(section){
    var scrolling = this[section]; //section would be 'theDiv' in this example
    scrolling.scrollIntoView({ block: "start", behavior: "smooth" });//corrected typo
  }

  render() {
    const totalList = [];
    for(let i = 0; i < 300; i += 1) {
        totalList.push(
            <div key={i}>{`hello ${i}`}</div>
        );
    }

    return (
      <div >
          {totalList}
          <div ref={(el) => this.theDiv = el}>
            this is the div I am trying to scroll to
          </div>
       </div>
         )
      };
    }

I also struggled with scrolling to the bottom of a list in react that's responding to a change in a redux store and I happened upon this and a few other stackoverflow articles related to scrolling. In case you also land on this question as well there are a few ways this could be a problem. My scenario was that I wanted a 'loading' spinner screen while the list was rendering. Here are a few wrong ways to do this:

  1. When loading = true, render spinner, otherwise render list.
{loading ? 
   <Spinner />
:
   <List />
}

as stated above this doesn't work because the list you might want to scroll to the bottom of isn't rendered yet.

  1. When loading set the display to block for the spinner and none for the list. When done loading, reverse the display.
<div style={{display: loading ? 'block' : 'none'>
   <Spinner />
</div>
<div style={{display: loading ? 'none' : 'block'>
   <List />
</div>

This doesn't work either since the list you want to scroll to the bottom of isn't actually being displayed likely when you call the scroll.

The better approach for the above scenario is to use a loading that acts as an overlay to the component. This way both the spinner and list are rendered and displayed, the scroll happens, and when the loading is complete, the spinner can be de-rendered or set to be invisible.

Related