Callback ref fails but string ref works


(Janaka Stevens) #1

Where a parent method refers to a ref seems to fail if called from a child.

afterScroll = () => {
  let thisElement = this.thumbDiv;
  let thisElementScrollTop = thisElement.scrollTop;
  if (thisElement.scrollHeight - thisElementScrollTop != thisElement.clientHeight) {
    thisElement.scrollTop = thisElementScrollTop - 100;
  }
};

in the child;

componentDidUpdate = () => {
  if ((this.props.index === this.props.selected)) {
    this.childDiv.scrollIntoView({behavior: 'smooth'});
    this.props.afterScroll();
  }
};

I tried binding the method and no joy. Any suggestions?


(Louis DeScioli) #2

Hey @calitek. Is calling this.props.afterScroll() throwing an exception, or is nothing happening?

You shouldn’t need to bind componentDidUpdate, it can be a normal class method

componentDidUpdate() {}

Can you include the relevant sections of the component render functions where you assign values for this.thumbDiv and this.childDiv?


(Janaka Stevens) #3

The result I get is “Uncaught TypeError: Cannot read property ‘scrollTop’ of null”. Here is the parent component;

export default class ThumbColumn extends React.Component {
  constructor(props) {
      super(props);
      this.afterScroll = this.afterScroll.bind(this);
  }
  componentDidMount = () => {
    this.thumbDiv.scrollTop = 0;
  };
  afterScroll = () => {
    let thisElement = this.thumbDiv;
    let thisElementScrollTop = thisElement.scrollTop;
    if (thisElement.scrollHeight - thisElementScrollTop != thisElement.clientHeight) {
      thisElement.scrollTop = thisElementScrollTop - 100;
    }
  };
  render() {
    let list = this.props.data.list.map((item, index) => {
      return (
        <ListItem
          key={index} item={item} index={index}
          selected={this.props.data.index}
          clickHandler={this.props.selectHandler}
          appState={this.props.appState}
          afterScroll={this.afterScroll}
          currentItem={this.props.currentItem}
          isCurrentColumn={this.props.isCurrentColumn}
        />
      );
    });
    return (
      <div id="ThumbColumnSty" ref={(ref) => { return this.thumbDiv = ref; }} style={ThumbColumnSty}>
        {list}
      </div>
    );
  }
}

(Janaka Stevens) #4

And this is the code that works.

export default class ThumbColumn extends React.Component {
  componentDidMount() {
    this.refs.thumbDiv.scrollTop = 0;
  };
  afterScroll = () => {
    let thisElement = this.refs.thumbDiv;
    let thisElementScrollTop = thisElement.scrollTop;
    if (thisElement.scrollHeight - thisElementScrollTop != thisElement.clientHeight) {
      thisElement.scrollTop = thisElementScrollTop - 100;
    }
  };
  render() {
    let list = this.props.data.list.map((item, index) => {
      return (
        <ListItem
          key={index} item={item} index={index}
          selected={this.props.data.index}
          clickHandler={this.props.selectHandler}
          appState={this.props.appState}
          afterScroll={this.afterScroll}
          currentItem={this.props.currentItem}
          isCurrentColumn={this.props.isCurrentColumn}
        />
      );
    });
    return (
      <div id="ThumbColumnSty" ref="thumbDiv" style={ThumbColumnSty}>
        {list}
      </div>
    );
  }
}

(Louis DeScioli) #5

You’re running into the case described here in the docs

If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one. You can avoid this by defining the ref callback as a bound method on the class, but note that it shouldn’t matter in most cases.

I’ve found that the “most cases” might be fewer than they think :sweat_smile:

What they’re describing looks like this:

class MyComponent extends Component {
  onDivMount = (node) => this.myDiv = node
  render() {
    return <div ref={this.onDivMount} />
  }
}

onDivMount will be called twice for the whole lifecycle of the instance, on mount and on dismount, instead of twice for every update.


(Janaka Stevens) #6

Sweet, it works.