Dealing with scroll position when you insert content is usually a difficult problem to solve. We'll see how to use React life cycle methods to solve it elegantly.

Insertion at the bottom

The first example is to maintain the scroll position at the bottom when an element is inserted at the bottom. A common use case is a chat application.

In order to scroll at the bottom, we can do that on componentDidUpdate. It happens every time the element is re-rendered.

componentDidUpdate: function() {
  var node = this.getDOMNode();
  node.scrollTop = node.scrollHeight;
},

But this is going to always scroll to the bottom, which can be very annoying if you want to read what was above. Instead you want to scroll only if the user was already at the bottom. To do that, we can check the scroll position before the component has updated with componentWillUpdate and scroll if necessary at componentDidUpdate

componentWillUpdate: function() {
  var node = this.getDOMNode();
  this.shouldScrollBottom = node.scrollTop + node.offsetHeight === node.scrollHeight;
},
 
componentDidUpdate: function() {
  if (this.shouldScrollBottom) {
    var node = this.getDOMNode();
    node.scrollTop = node.scrollHeight
  }
},

Note: we use this.shouldScrollBottom = ...; and not this.setState({shouldScrollBottom: ...}); because we don't want to trigger another render. We just need to manage that value between the two events.

Insertion at the top

The other use case is adding elements at the top of the page but doing so without screwing up the current scroll position of the user. An example is a log view where you can scroll to the top to read historical context.

This is using a similar technique. On componentWillUpdate we store the scroll position and on componentDidUpdate we scroll to the added delta.

componentWillUpdate: function() {
  var node = this.getDOMNode();
  this.scrollHeight = node.scrollHeight;
  this.scrollTop = node.scrollTop;
},
 
componentDidUpdate: function() {
  var node = this.getDOMNode();
  node.scrollTop = this.scrollTop + (node.scrollHeight - this.scrollHeight);
},

Conclusion

React has not been designed to handle scroll position natively. However, it provides escape hatches from the declarative paradigm in order to be able to implement them.

If you liked this article, you might be interested in my Twitter feed as well.
 
 

Related Posts

  • September 24, 2011 Javascript: Cyclic Object Detection (17)
    URLON.stringify() suffer from a problem, when passed an object that contains a cycle, it will never stop. This article shows 3 techniques in order to detect if an object is cyclical. Edit the object: Mark In order to detect a cycle in an object, the method we learn at school is to […]
  • September 14, 2011 CSS – One Line Justify (32)
    I came across a CSS problem, text-align: justify does not work with only one line. Justify behavior The reason is because it has been designed with paragraphs in mind. It justifies all the lines but the last one. Normal Justify Lorem ipsum dolor sit amet, consectetur […]
  • January 25, 2017 Anatomy of a JavaScript Pretty Printer (10)
    During the past few weeks, I've been working on prettier, which is a JavaScript pretty printer. We are approaching the phase where we can actually use it so this is a good time to explain how it works. We're going to go through an example if (!pretty) { makePretty() } String […]
  • August 25, 2009 Test – Are you a Javascript Guru? (2)
    Javascript is a very flexible language, I made a compilation of some edge cases that you may have encountered while programming. The main goal is to point out some interesting specific behaviors. Concatenation 1] var result = [10] + 1; [10, 1] "101" 11 Explanation: The Array […]
  • July 7, 2012 CSS – Understanding Percentage Background Position (10)
    In this article, I'm going to guide you through a concrete problem I had to solve. Eventually, we'll see how to use percentage values in the background-position CSS property and how it solves a lot of tough issues. Usual way Positioning the image The usual way to position images […]