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!
source to share
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.
source to share
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")
);
source to share