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.
 
  • James I.

    I especially like your second example. It is really common for sites with real-time streams to insert content above the fold, and it almost never makes sense to shift down the content that the user is currently reading. In the longer-term I think CSS and the browser should implement this behavior for us. I brought it up awhile back on the CSS WG list but it didn't really catch on: http://lists.w3.org/Archives/Public/www-style/2012Sep/0089.html.

    In the meantime, handling this with React is really nice since all of the componentWillUpdate calls can run before React modifies the DOM, nicely batching together all layout reads before dirtying the layout. Yet another example of how React avoids layout thrash in a practical manner.

 

Related Posts

  • September 24, 2011 -- Javascript: Cyclic Object Detection (3)
    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 mark each v...
  • September 14, 2011 -- CSS – One Line Justify (13)
    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 adipisicing elit,...
  • November 4, 2013 -- Bitwise Truthiness (0)
    In this blog post, I explore another form of truthiness in Javascript. What happens if you use a bitwise operator on a value like 0|value or ~~value. Context We recently turned on the JSHint bitwise rule by default and the following code was caught. var isValid = false; for (var i = 0...
  • August 25, 2009 -- Test – Are you a Javascript Guru? (0)
    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 doesn't ...
  • July 7, 2012 -- CSS – Understanding Percentage Background Position (6)
    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 inside a...