Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm trying to get the height of a stateless child component, so that I'm able to use its height within a 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 a Codepen with the error.

Update:

Actual code/context

So I've currently got a Header component, which looks like so:

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 instances, I want to add logic to this Header to animate it (for example, on the homepage when the user scrolls, I am animating the Header to become fixed when they scroll past a certain point - a sticky header, if you will).

The way I've done this before was to just have a separate Class for a Header with particular functionality (as described above) and one without. But in order to keep my code DRY I have separated out the Header to be its own functional stateless component, with the view to wrap it in Class that gives it the sticky header functionality.

Here's the 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 problem - I hope that makes things a little more clearer.

P.s sorry for the lack of context, I imagined the answer would be more straight forward than what it seems!

like image 814
A7DC Avatar asked Aug 05 '17 19:08

A7DC


People also ask

How do you determine the height of a child's reaction?

To get the parent height and width in React: Set the ref prop on the element. In the useEffect hook, update the state variables for the height and width. Use the offsetHeight and offsetWidth properties to get the height and width of the element.

How do you find the height of the functional component in React?

To get a rendered component height with React, we can use the element's clientHeight property. We assign the ref ref to the ref prop of the div. Then we call useEffect with a callback and an empty array to get the element's height when the component mounts.

What is a stateless functional component?

Stateless components are those components which don't have any state at all, which means you can't use this. setState inside these components. It is like a normal function with no render method. It has no lifecycle, so it is not possible to use lifecycle methods such as componentDidMount and other hooks.


Video Answer


2 Answers

Ok, so first of all, thank you to everyone for their replies - it's really appreciated!

I began implementing the react-waypoints as recommended by Win, although it became apparent I would need to modify my code a bit more to make it work, so I figured I'd have one last search to try and find an alternative solution using refs.

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

The solution

Because React doesn't allow refs on stateless functional component, instead you can wrap the functional component inside a wrapper while giving the wrapper its own ref, like so:

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

You can then 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 might not be the most elegant solution, it certainly solves my issue for the time-being.

Here is a Codepen for reference.

like image 95
A7DC Avatar answered Oct 20 '22 01:10

A7DC


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

I've added a debounce to the onEnter and onLeave so that it doesn't update the state like crazy, this is so we can optimise the performance when scrolling and it won't lock up the application. Again, it's a very rough idea of what you can do and there's plenty of options to improve the implementation.

=== Previous

This is one way that you can get around the issue by keeping the stateless component. Since the context wasn't explained further, this should give you an opportunity to see how you can grab the height of a child component that sits within 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")
);
like image 1
Win Avatar answered Oct 20 '22 00:10

Win