简体   繁体   中英

Access parent's Ref in child component on child mount

I have the following Parent/Child components. I need to scroll to top as soon as the Child component is initially rendered.

import React, {Component} from 'react';
import Child from "./Child";

class Parent extends Component {

  render() {
    return (
      <div>
        <div ref={topDiv => {
          this.topDiv = topDiv;
        }}/>
        <Child
          topDiv={this.topDiv}
        />
      </div>
    );
  }
}

export default Parent;
import React, {Component} from 'react';

class Child extends Component {

  componentDidMount() {
    this.scrollToTop();
  }

  // componentWillReceiveProps(nextProps, nextContext) {
  //   nextProps.topDiv.scrollIntoView();
  // }

  scrollToTop() {
    this.props.topDiv.scrollIntoView();
  }

  render() {
    return (
      <div style={{height: '500rem'}}>
        Long Child
      </div>
    );
  }
}

export default Child;

Using this, I get the error:

TypeError: Cannot read property 'scrollIntoView' of undefined

I think this is because when componentDidMount is called, the props have not been received yet, so topDiv is null/undefined.

If I use componentWillReceiveProps as shown in the commented part, it works fine. But I can't use it because:
1. It is deprecated.
2. I think it will be called every time props are received. So I guess I'll need to keep a variable to know if it's the first time props are being received?

I can't use componentDidUpdate because the documentation says " This method is not called for the initial render. ".

How to do something when props are received the first time after a component is rendered?

Props are definitely supposed to be available in componentDidMount .

This seems like it could be stemming from some messiness from the React lifecycle.

In your parent, you're providing a ref callback in a render at approximately the same time you're mounting Child . According to the React docs :

React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts. Refs are guaranteed to be up-to-date before componentDidMount or componentDidUpdate fires.

However, componentDidMount of the children will fire before the componentDidMount of their parent, so this guarantee doesn't mean Parent 's this.topDiv will be defined before Child calls componentDidMount . (And, thinking on it further, it definitely doesn't guarantee that it'll be defined before being provided to Child as a prop.)

In your parent, you could maybe try something like

componentDidMount() {
    this.setState({ divSet: true });
}

render() {
    let child = null;
    if (this.state.divSet) {
        child = <Child topDiv={this.topDiv} />
    }

    return (
      <div>
        <div ref={topDiv => {
          this.topDiv = topDiv;
        }}/>
        {child}
      </div>
    );
  }

This will guarantee that your ref is set before Child mounts. The setState is specifically used to force the parent to rerender when the ref is set.

What you are trying to do is by definition, impossible! When you say

I need to scroll to top as soon as the Child component is initially rendered.

you mean the Parent's top right?

But the key detail here is

As soon as your child has completed rendering, your parent has not yet been rendered! There is no top to scroll to Yet!


Life Cycles of a component

This is the typical lifecycle flow of a component

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount()

When it comes to a parent child relationship it will be

  1. [Parent]constructor()
  2. [Parent]componentWillMount()
  3. [Parent]render()
    1. [Child]constructor()
    2. [Child]componentWillMount()
    3. [Child]render()
    4. [Child]componentDidMount() - Parent does not exist yet
  4. [Parent]componentDidMount()

Also a small note on the newly added lifecycle method in place of componenWillRecieveProps which is

getDerivedStateFromProps(props, state)

getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.


So your best bet is to call the scroll top within your paren't component componentDidMount where you will have access to it's ref.

If you are still determined to do this in the child component, you will have to use getDerivedStateFromProps in combination with a flag in state to detect whether you have already scrolled to the top at least once. This can be a bit efficient and dirty overall.

Check here for a cool flow diagram of component lifecycle for clarity!

Bind this.scrollToTop to your class component in the constructor :

class Child extends Component {
  constructor() {
    this.scrollToTop = this.scrollToTop.bind(this);
  }
  ...rest of your component code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM