13

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>
  )

}; }

Jamal
  • 811
  • 5
  • 15
RamiroIsBack
  • 260
  • 1
  • 3
  • 15
  • 1
    Are you sure scrollIntoView is supported on the browser version you are using? https://developer.mozilla.org/en/docs/Web/API/Element/scrollIntoView#Browser_compatibility – Finbarr O'B Jul 13 '17 at 10:24
  • 1
    Also, it may help to check if 'input' is truthy in your ref callback, remember the ref is called with the element on mount and null on unmount (https://facebook.github.io/react/docs/refs-and-the-dom.html) – Finbarr O'B Jul 13 '17 at 10:26
  • Yes, I'm using Chrome Version 59.0.3071.115 (Official Build) (64-bit) and it says 29, I guess that if it's greater that that it's supported. At first I was tempted to use element.scrollIntoView({block: "start", behavior: "smooth"}) but read not many browser supported parameters yet so I sticked to basic support. I also tryed boolean parameter (true and false) just in case but doen't help thanks for the hint @Finbarr O'B – RamiroIsBack Jul 13 '17 at 11:04
  • 2
    The simplest truthy check is to do `if(input){ this.textInput = input; }`, but I suspect this isn't your problem. I've just tried to run your code, and it appears to work in my case. I had to include componentDidMount() however as I was not manipulating state anywhere in my component (componentDidUpdate() is only called if the component state is changed). I also removed the bootstrap CSS classes. See here: https://github.com/finbarrobrien/reacty/blob/master/src/App.js – Finbarr O'B Jul 13 '17 at 11:30
  • I just cloned your github 'reacty' and works fine also on my end, I just tried removing the bootstrap CSS classes as you did but still not working, now that I know it works on this simpler version , i'm gonna work on simplifying the code to make it work and start there , thanks i'll keep you posted – RamiroIsBack Jul 13 '17 at 11:56
  • Ok, in componentDidMount works just fine, but in componentDidUpdate (where I need it) it scrolls up automatically so I'm trying to find a way to prevent default but I don't know how to get the event or context to do so. Reading this entry: https://stackoverflow.com/questions/35522220/react-ref-with-focus-doesnt-work-without-settimeout-my-example I found a work-around setting a TimeOut in componentDidUpdate to trigger the scrollIntoView() this will do for now but I would like to do it the right way – RamiroIsBack Jul 14 '17 at 07:44

2 Answers2

5

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>
         )
      };
    }
MhkAsif
  • 537
  • 3
  • 18
RamiroIsBack
  • 260
  • 1
  • 3
  • 15
  • 3
    I am using functional component so cannot use componentWillRecieveProps so i am using setTimeOut with 0. and thank you for tip :) – agravat.in May 26 '20 at 09:32
0

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
evesnight
  • 680
  • 4
  • 7