How do you get the height of a stateless functional component that is a child?

I am trying to get the height of a stateless child component, so that I can use its height in the parent class, but I am getting the following error: Invariant Violation: Stateless function components cannot have refs.

Simplified code

Parent class

class App extends React.Component {
  componentDidMount() {
    console.log(this.header);
  }
  render() {
    return (
      <Child ref={component => this.header = component} />
    )
  }
}

      

Child

const Child = () => (
  <header ref="header">
    Some text  
  </header>
)

      

Is there a way to do this?

Here is a link to the Codepen with the error .

Update:

Actual code / context

So, I have a Header component that looks like this:

export const Header = ({dateDetailsButton, title, logo, backButton, signUpButton, inboxButton, header}) => (
    <header header className="flex flex-row tc pa3 bb b--light-gray relative min-h-35 max-h-35">
      {signUpButton ? <Link to="/edit-profile-contact" href="#" className="flex z-2"><AddUser /></Link> : null }
      {backButton ? <BackButton className="flex z-2" /> : null }
      <h1 className={logo ? "w-100 tc dark-gray lh-solid f4 fw5 tk-reklame-script lh-solid ma0" : "w-100 tc dark-gray lh-solid f4 fw4 absolute left-0 right-0 top-0 bottom-0 maa h1-5 z-0"}>{title}</h1>
      {dateDetailsButton ? <Link to="/date-details" className="absolute link dark-gray right-1 top-0 bottom-0 maa h1-5 z-2">Details</Link> : null }
      {inboxButton ? <Link to="/inbox" href="#" className="flex mla z-2"><SpeechBubble /></Link> : null}
    </header>
)

      

In some cases, I want to add logic to that header to animate it (for example, on the master page, when the user scrolls, I animate the header to lock in when they scroll past a certain point - a sticky header, if you will).

The way I did it before was just to have a separate class for the header with specific functionality (as described above) and without it. But to keep my code DRY, I have separated the header as my own stateless functional component in order to wrap it in a class, which gives it the sticky header functionality.

Here's a class for that:

export default class FeedHeader extends React.Component {

    constructor(props) {
        super(props);
        this.handleScroll = this.handleScroll.bind(this);
        this.state = {
            scrolledPastHeader: null,
            from: 0,
            to: 1,
        }
    }

    componentDidMount() {
        window.addEventListener('scroll', this.handleScroll);
        this.setState({navHeight: this.navHeight.offsetHeight});
    }

    componentWillUnmount() {
        window.removeEventListener('scroll', this.handleScroll);
    }

    handleScroll(e) {
        const { navHeight, scrolledPastHeader } = this.state;
        const wy = document.body.scrollTop;
        if (wy < navHeight) this.setState({scrolledPastHeader: null})
        else if (wy > navHeight && wy < 100) {
            this.setState({scrolledPastHeader: false})
        }
        else this.setState({scrolledPastHeader: true})
    }

    getMotionProps() {
        const { scrolledPastHeader } = this.state;

        return scrolledPastHeader === false ?
        {
            style: {
                value: this.state.from
            }
        }
        : {
            style: {
                value: spring(this.state.to)
            }
        }
    }

    render() {
        const { brand, blurb } = this.props;
        const { scrolledPastHeader } = this.state;
        return (
            <Motion {...this.getMotionProps()}>
                {({value}) => {
                    return (
                        <Header ref="child" title={this.props.title} backButton={false} signUpButton inboxButton logo/>
                    );
                }}
            </Motion>
        )
    }
}

      

So this is the context for this particular issue - I hope this makes things a little clearer.

I'm sorry for the lack of context, I imagined that the answer would be more direct than what it seems!

+3


source to share


2 answers


Okay, so first of all, thanks to everyone for their responses - it is really appreciated!

I started to implement react-waypoints

as Win recommended, although it became apparent that I would need to change my code a bit for it to work, so I figured I would have one last search to try and find an alternative with refs

.

The answer I was looking for was actually quite simple, and came from the following Github issue thread mnpenner .

Decision

Since React does not allow stateless functional component references, you can instead wrap the functional component inside a wrapper, giving the wrapper its own ref

, for example:



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

      

Then you can access the height of the component by targeting the wrapper:

  componentDidMount() {
        if(this.childWrap && this.childWrap.firstChild) {
        let childHeight = this.childWrap.offsetHeight;
                console.log(childHeight);
    }
  }

      

While this may not be the most elegant solution, it certainly solves my problem for the time being.

Here's the Codepen for reference.

+4


source


Here's a solution using React-waypoint. You can check it out here: https://codesandbox.io/s/qp3ly687

I added a debounce for onEnter and onLeave so that it doesn't update the state like crazy, so we can optimize the scrolling performance and not lock the app. Again, this is a very rough idea of ​​what you can do, and there are many options for improving implementation.

=== Previous



This is one way that you can get around the problem by keeping the component stateless. Since the context has not been explained further, this should give you a chance to see how you can grab the height of a child component that is inside a stateless component.

class App extends React.Component {
  componentDidMount() {
    console.log(this.child.offsetHeight);
  }
  render() {
    return (
      <div>
        <Wrapper>
          <div 
            ref={child => this.child = child}
            style={{height: 500}}>
            Some text!!
          </div>
         </Wrapper>
       </div>
    )
  }
}

const Wrapper = ({children}) => {
  return (
    <div>
      <header>Header</header>
      <div>{children}</div>
      <footer>Footer</footer>
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById("main")
);
      

Run codeHide result


+1


source







All Articles