4
votes

What I'm doing:

Simple - I need to scroll to the bottom of a message list as soon as the component contains it (Messages) is mounted.

The Monkey Wrench In My Program :)

Scrolling to bottom after mounting was fine until...

I simply moved some furniture around by taking the header of a scroll-able child component(Messages) and moved it into the it's parent component(MessageSection) in order to get the header to stick.

Here is a BEFORE and an AFTER:

Before(Yay it work!) MessageSection.js

if(this.state.messages !== null) 
  return (
    <ScrollArea speed={0.8} className="area" contentClassName="content">
      <Messages  messages={this.state.messages} />
    </ScrollArea>
  );
}else{

  return (
    <ScrollArea speed={0.8} className="area" contentClassName="content">
      <EmptyMessages />
    </ScrollArea>
  );
}

The Simple Diff:

(Basically just moved the Heading of Messages into the parent MessageSection)

  1. Pulled the header out of the scrolling child component(Messages).

  2. Put the header above the ScrollArea

  3. Then to render the new JSX -I Wrapped the Header and ScrollArea in a new div ("messages")

AFTER:(What! Y U NO Working??)

MessageSection.js

if(this.state.messages !== null) {
  return (
    <div className="messages">
      <div className ="messages-section-header">
        <span className="header">{this.props.name}</span>
      </div>
      <ScrollArea speed={0.8} className="area scrollarea-flex" contentClassName="content">
        <Messages  messages={this.state.messages} />
      </ScrollArea>
    </div>
  );
}else{
  return (
    <div className="messages">
      <ScrollArea speed={0.8} className="area scrollarea-flex" contentClassName="content">
        <EmptyMessages />
      </ScrollArea>
    </div>
  );
}

The Error:

After the restructuring I got this error from ScrollArea.jsx#L222

Uncaught TypeError: Cannot read property 'offsetHeight'


The Cause of the Error:

When scroll-able component Messages mounts in it's componentDidMount it tries to scroll to the bottom using a exposed context of the ScrollArea component, but refs inside the ScrollArea do not exist. Which is weird because the refs will exist if called later by componentDidUpdate

For Reference, here is the child component where the failure occurs.

MessageList.js(Scroll-able component that tries to scroll to bottom as it mounts)

contextTypes: {
  scrollArea: React.PropTypes.object //Used by the Library to Access the Scrolling Panel(The Parent Component)
},

componentDidMount: function() {
    this._scrollToBottom(); 
},

_scrollToBottom: function() {
    var scrollHeight = this.refs.messages.scrollHeight; 
    this.context.scrollArea.scrollYTo(scrollHeight);//ERROR -the refs will not exist inside context as expected
}

Why I Am Baffled:

It doesn't make sense how ScrollArea can be mounted and that it's child Messages can also be mounted and calling the defined context ScrollArea exposed to it, yet the refs of ScrollArea don't exist??? Which is double confusing since it worked before I did the little change.

Question:

How could a simple change in the structure of my code cause the context to not have access to it's refs

1
have you tried setting the refs using a callback?enjoylife
Just as an update I tried restructuring the code in a similar way as it was previously and the issue still exists. So a better explanation is that it previously was working on a fluke.Nick Pineda
My best guess is that now that the scroll area is getting its height from flex-grow: 1; it must be some sort of mounting issue like @brandon explained in his answer.Nick Pineda

1 Answers

4
votes

A parent component is not "mounted" until after all of its children are "mounted". So your ScrollArea is still in the process of "mounting" when your Message componentDidMount is executing.

Your problem is a result of the way you've modeled the interaction: You have a "child" component dictating behaviors of some parent components. If you put your parent component in charge of this scroll behavior, then everything will work.

Specifically, add a componentDidMount method to your MessageSection component and put the logic there:

componentDidMount: function() {
    // Assume you add `ref="messages"` to your render method so you can
    // get a reference to the child component
    this.refs.messages._scrollToBottom();
}