ReactJS get rendered component height - reactjs

I'm attempting to integrate or create a React version of https://github.com/kumailht/gridforms, to do so I need to normalize the height of the columns inside of the row. The original takes the height of the grid row and applies it to the children columns.
I had planned to get the height of the row and then map it to a property of the child, though from my attempts I'm thinking this might not be the ideal way or even possible?
Below is my current code.
GridRow = React.createClass({
render(){
const children = _.map(this.props.children, child => {
child.props.height = // somehow get row component height
return child
})
return (<div data-row-span={this.props.span} {...this.props}>
{children}
</div>)
}
})
GridCol = React.createClass({
render(){
return (<div data-field-span={this.props.span} style={{height:this.props.height}} {...this.props}>
{this.props.children}
</div>)
}
})
I tested setting the style this way and it will work, however getting the height isn't.
EDIT: Fiddle:
https://jsfiddle.net/4wm5bffn/2/

A bit late with the answer but technically you can get element hight this way:
var node = ReactDOM.findDOMNode(this.refs[ref-name]);
if (node){
var calculatedHeight = node.clientHeight;
}

According to current React docs, the preferred use of refs is to pass it a callback rather than a string to be accessed elsewhere in this.refs.
So to get the height of a div (within a React.Component class):
componentDidMount() {
this.setState({ elementHeight: this.divRef.clientHeight });
}
render() {
return <div ref={element => this.divRef = element}></div>
}
Or it works this way, though I don't know if this is advisable since we set state in the render method.
getHeight(element) {
if (element && !this.state.elementHeight) { // need to check that we haven't already set the height or we'll create an infinite render loop
this.setState({ elementHeight: element.clientHeight });
}
}
render() {
return <div ref={this.getHeight}></div>;
}
Reference: https://facebook.github.io/react/docs/more-about-refs.html

Don't know about anyone else but I always have to get it on the next tick to be sure of getting the correct height and width. Feels hacky but guessing it's to do with render cycle but I'll take it for now. onLayout may work better in certain use cases.
componentDidMount() {
setTimeout(() => {
let ref = this.refs.Container
console.log(ref.clientHeight)
console.log(ref.clientWidth)
}, 1)
}

Here is an example of using refs and clientWidth/clientHeight:
import React, { Component } from 'react';
import MyImageSrc from './../some-random-image.jpg'
class MyRandomImage extends Component {
componentDidMount(){
let { clientHeight, clientWidth } = this.refs.myImgContainer;
console.log(clientHeight, clientWidth);
}
render() {
return (
<div ref="myImgContainer">
<img src={MyImageSrc} alt="MyClickable" />
</div>
);
}
}
export default MyRandomImage;
Note: this appears to work for width reliably, but not height. Will edit if I find a fix...

My personal opinion is to try and avoid using static and measured sizes like this if you can avoid it because it can complicate the application unnecessarily. But sometimes you cannot get around it. Your component will need to be mounted before you can get a size from it.
General approach:
Give the element a ref
When the element is rendered, grab the ref and call .clientHeight and/or .clientWidth
Put the values on the state or pass with props
Render the element that needs the size from the state variables
In your case you want to grab the size of a column you can do something like:
GridRow = React.createClass({
render(){
const children = _.map(this.props.children, child => {
child.props.height = // somehow get row component height
return child
})
return (<div data-row-span={this.props.span} {...this.props}>
<GridCol onSizeChange={(size) => {
//Set it to state or whatever
console.log("sizeOfCol", size);
}} />
</div>)
}
})
GridCol = React.createClass({
componentDidMount(){
//Set stizes to the local state
this.setState({
colH: this.col.clientHeight,
colW: this.col.clientWidth
});
//Use a callback on the props to give parent the data
this.props.onSizeChange({colH: this.col.clientHeight, colW: this.col.clientWidth})
}
render(){
//Here you save a ref (col) on the class
return (<div ref={(col) => {this.col = col}} data-field-span={this.props.span} style={{height:this.props.height}} {...this.props}>
<.... >
</div>)
}
})

According this answer sizes of a component can be turned out having zero width or height inside componentDidMount event handler. So I'm seeing some ways to solve it.
Handle the event on top-level React component, and either recalculate the sizes there, or redraw the specific child component.
Set the load event handler on the componentDidMount to handle loading the cells into the react component to recalculate the proper sizes:
componentDidMount = () => {
this.$carousel = $(this.carousel)
window.addEventListener('load', this.componentLoaded)
}
Then in the componentLoaded method just do what you need to do.

A bit more late, but I have an approach which can be used without using the getElementById method. A class based component could be created and the sample code can be used.
constructor(props) {
super(props);
this.imageRef = React.createRef();
}
componentDidMount(){
this.imageRef.current.addEventListener("load", this.setSpans);
}
setSpans = () => {
//Here you get your image's height
console.log(this.imageRef.current.clientHeight);
};
render() {
const { description, urls } = this.props.image;
return (
<div>
<img ref={this.imageRef} alt={description} src={urls.regular} />
</div>
);
}

Above solutions are good. I thought I'd add my own that helped me solve this issue + others discussed in this question.
Since as others have said a timeout function is unpredictable and inline css with javascript variable dependencies (ex. style={{height: `calc(100vh - ${this.props.navHeight}px)`}}) can alter the height of elements after the componentDidMount method, there must be an update after all of the elements and inline javascript-computed css is executed.
I wasn't able to find very good information on which elements accept the onLoad attribute in React, but I knew the img element did. So I simply loaded a hidden image element at the bottom of my react component. I used the onLoad to update the heights of referenced components elsewhere to yield the correct results. I hope this helps someone else.
_setsectionheights = () => {
this.setState({
sectionHeights: [
this.first.clientHeight,
this.second.clientHeight,
this.third.clientHeight,
]
});
}
render() {
return (
<>
<section
ref={ (elem) => { this.first = elem } }
style={{height: `calc(100vh - ${this.props.navHeight}px)`}}
>
...
</section>
...
<img style={{display: "none"}} src={..} onLoad={this._setsectionheights}/>
</>
);
}
For the sake of being thorough, the issue is that when the componentDidMount method is executed, it only considers external css (speculation here). Therefore, my section elements (which are set to min-height: 400px in external css) each returned 400 when referenced with the clientHeight value. The img simply updates the section heights in the state once everything before it has loaded.

I'd rather do it in componentDidUpdate, but by making sure a condition is met to prevent an infinite loop:
componentDidUpdate(prevProps, prevState) {
const row = document.getElementById('yourId');
const height = row.clientHeight;
if (this.state.height !== height) {
this.setState({ height });
}
}

Related

How can you create Refs after the component has mounted in React?

My case requires that I use React.createRef() for each time picker I have (there is a certain functionality in the time pickers that can be only used through Refs).
When the page is mounted, I have initially 2 time pickers. All works well when I have only two, but my work is ruined because I am required to Add/Remove Time Pickers using buttons. So I am able to add the time pickers easily.
But now my question is how do I create the refs ? My declaration for React.createRef() is in the Constructor(){} for the first 2 refs.
The question is where do I instantiate the refs for the time pickers that are added onClick ?
Thank you
You should wrap your time picker in an another component, create the ref there and perform the work that requires a ref inside of that components then forwarding the result via props (you can pass a function via a prop).
For example, you could give each component an unique ID (uuid does a great job of that), pass that via a prop, pass a value and pass a function that accepts an ID and a value, then call that function whenever a result from the ref is obtained.
You could do something like this, but it requires you to have a unique identifier per component that should not be the index. (Cause this can change)
Pseudo Code
class Wrapper extends Component {
construct() {
...
this.refsById = {}
}
getRefOrCreate(id) {
if(_has(this.refsById[id]) {
return this.refsById[id];
} else {
this.refsById[id] = React.createRef();
return this.refsById[id];
}
}
onClickHandler(value, id) {
const ref = this.refs[id];
const { onClick } = this.props;
}
render(){
// Here you need to know how many pickers you need, and their id
const { pickersInformationArray} = this.props;
return (
<div> { pickersInformationArray.map((id) => <TimePicker ref={this.getRefOrCreate(id);} onClick={(value) => { this.onClickHandler(value, id); } } )} </div>
)
}
I found the solution.
Let me first say that I was using the method of creating ref in the constructor in my incorrect solution
class DummyClass extends Component {
constructor(){
this.timePickerRef = React.createRef();
}
}
render() {
let timers = array.map( index => {
<TimePicker
ref={timepicker => timePickerRef = timepicker}
value={00}
onChange={(data) =>
{this.handleTimePcikerValueChange(data, index);}}
/>
}
return (
timers
)
}
}
what I instead did was the following
disregard and remove this.timePickerRef = React.createRef() since it will no longer be necessary
while the code in handleTimePcikerValueChange & render will be as follows:
handleTimePcikerValueChange = (value, index) => {
// do whatever manipulation you need
// and access the ref using the following
this[`timePicker_${index}`]
}
render() {
let timers = array.map( index => {
<TimePicker
ref={timepicker => this[`timePicker_${index}`] = timepicker}
value={00}
onChange={(data) =>
{this.handleTimePcikerValueChange(data, index);}}
/>
}
return (
timers
)
}
I didn't post the code that handle adding time pickers because it is irrelevant.
I would like to thank those who responded !

this.forceUpdate() not re-rendering dynamically created components

Assume all the various components have been defined.
In my react component, I want the button click to trigger the appending of a new TextBox component in my dynamically created questions component. When I tested the button click with forceUpdate(), a TextBox was successfully appended to questions but there was no apparent addition of a new TextBox element. I tested whether the component was actually re-rendering by using <h4>Random number : {Math.random()}</h4> and it turns out the component was doing so, as the number changed every time I pressed the button.
Is something being done wrong?
constructor (props) {
super(props);
this.questions = [];
this.questions.push(<TextBox key={this.questions.length}/>);
this.createTextBox = this.createTextBox.bind(this);
this.loadTextBox = this.loadTextBox.bind(this);
}
createTextBox() {
this.questions.push(<TextBox key={this.questions.length}/>);
this.forceUpdate();
}
loadTextBox() {
return (this.questions);
}
render() {
return(
<div>
<h4>Random number : {Math.random()}</h4>
{this.loadTextBox()}
<ButtonToolbar className="add-question">
<DropdownButton bsSize="large" title="Add" id="dropdown-size-large" dropup pullRight>
<MenuItem eventKey="1" onClick={this.createTextBox}>Text Box</MenuItem>
</DropdownButton>
</ButtonToolbar>
</div>
);
}
Only items inside this.state are properly monitored by React on whether or not a rerender should occur. Using this.forceUpdate does not check to see if this.questions has been changed.
Use this.questions as this.state.questions. When you do this, do not mutate this.state.questions. Instead, make a new copy of it and use this.setState on it.
constructor (props) {
super(props);
this.state = {
questions: [<TextBox key={0}/>]
}
this.createTextBox = this.createTextBox.bind(this);
this.loadTextBox = this.loadTextBox.bind(this);
}
createTextBox() {
const newQuestions = [...this.state.questions, <TextBox key={this.questions.length}/>]
// or you can use
// const newQuestions = this.state.questions.concat(<TextBox key={this.questions.length + 1}/>)
this.setState({questions: newQuestions})
}
loadTextBox() {
return (this.state.questions);
}
One important thing to note is that this.forceUpdate is almost never needed. If you find yourself using it, you are writing your code in an unoptimal way. I made some modifications to your code regarding how keys are assigned. The only reason you should ever be checking for updates is if something in this.state has changed, which involves using this.setState.

onScroll React cannot trigger

why if i using onScroll react version 16 cannot fire how to make it work. because i want using onScroll than onWheel?.
import ReactDom from 'react-dom'
constructor(props) {
super(props);
this._fireOnScroll = this.fireOnScroll.bind(this);
}
fireOnScroll() {
console.log('Fire!');
}
componentDidMount() {
const elem = ReactDOM.findDOMNode(this.refs.elementToFire);
elem.addEventListener('scroll', this._fireOnScroll, true);
}
componentWillUnmount() {
const elem = ReactDOM.findDOMNode(this.refs.elementToFire);
elem.removeEventListener('scroll', this._fireOnScroll);
}
render() {
return (
<div ref="elementToFire">
<AnotherComponent imagesUrl={Array(100)}}>}
</div>
</div>
);
}
Your div is empty. scroll only fires if actual scrolling did happen.
So, your div needs to be smaller than its content and needs to show scrollbars.
See this pen for a working example.
However, there is no reason for using a ref. Simply use onScroll:
<div style={{height: 75, width: 100, overflow:'scroll'}} onScroll={this.fireOnScroll}>
See this pen for a working example.
Your ref is not properly assigned to the div it should be
<div ref = {this.elementToFire} >
If not works then try
const elem = ReactDOM.findDOMNode(this.refs.elementToFire.current);
Also from the code you have given O guess you missed to create a ref object also
this.elementToFire = React.createRef();
Hope it will work

get height of image on load and send to parent

I am trying to get the height of an image when it has loaded and send it back to the parent component, but it is causing infinite rerendering.
This is a prototype of my code:
import MyImage from './images/myImage.jpg';
class Image extends React.Component {
constructor(props) {
super(props);
this.state = {
height: 0
}
}
getHeight = (e) => {
const height = e.target.getBoundingClientRect().height;
this.setState({
height: height
});
this.props.setUnitHeight(height);
}
render() {
const image = this.props.image;
return (
<img src={image.name} onLoad={(e)=>{this.getHeight(e)}} />;
);
}
}
class App extends Component {
constructor(props) {
super(props);
const initUnit = 78.4;
this.state = {
unit: initUnit
}
}
setUnitHeight = (height) => {
this.setState({
unit: height
});
}
render() {
return (
<div>
<Image image={MyImage} setUnitHeight={this.setUnitHeight} />
</div>
);
}
}
I have tried sending unit as a prop and then checking in shouldComponentUpdate whether it should be rerender or not, but that did nothing.
The issue you are having is that React by default re-renders the component every time you call this.setState. In your case what this is happening:
You load your Image component
It loads the <img> tag and fires the onLoad function
The onLoad function calls this.setState
Repeat these steps forever
You should take a look at the React's lifecycle components methods (https://reactjs.org/docs/react-component.html#the-component-lifecycle) to understand this better.
My suggestion is: do not keep the image height in the state, unless you really need it. If you really need to maintain it in the state for some reason you can use the lifecycle method shouldComponentUpdate (https://reactjs.org/docs/react-component.html#shouldcomponentupdate`) to prevent it from rendering.
Your code seems redundant, setState({}) isn't necessary in <Image> class. If you are using the same props throughout the app, then you should be setting it at one place and be using the same prop all over. For example -
getHeight = (e) => {
const height = e.target.getBoundingClientRect().height;
//setState not needed here
this.props.setUnitHeight(height);
}
That should do it.
P.S: Do check if your this references aren't going out of scope.

Is there a way to access a React component's sub-components?

So I know that you can access a component's children with this.props.children:
<MyComponent>
<span>Bob</span>
<span>Sally</span>
</MyComponent>
Which is great if I'm interested in Bob and Sally, but what if I want to interact with the components that make up MyComponent (i.e. Subcomp1 and Subcomp2 shown below)?
render: function() {
return (
<div className="my-comp">
<Subcomp1 />
<Subcomp2 />
</div>
);
},
Use Case
I'm trying to create a higher order component that manages the tab index (roving tab index: https://www.w3.org/TR/wai-aria-practices/#kbd_roving_tabindex) of the wrapped component's sub-components, so it would be great if I could get a ref to the wrapped component and filter it's subcomponents by type.
So far the only approach that seems possible is to have each component store a ref for each of it's subcomponents, but this is tedious and kind of defeats the purpose of an HOC. Is there a generic way to access these sub-components?
A rough example of what I'm trying to do:
var HOC = (ComposedComponent) => {
return React.createClass({
componentDidMount: function() {
const subComponents = this.composedComponent.subComponents; // Something like this would be nice
const menuItems = subComponents.filter(() => {
// figure out a way to identify components of a certain type
});
this.applyRovingTabIndex(menuItems);
},
render: function() {
return (
<ComposedComponent
ref={(c) => { this.composedComponent = c }}
{...this.props} />
);
}
});
};
The tabIndex manipulation need not be done in the HOC, rather it can be done in the Parent component that renders all the HOCs. Because all you need is to determine which sub component is clicked and adjust the selected state on the Parent component. This selected state can then be propagated back to the sub components who compare their index with selected index and assign tabIndex accordingly.
You can send the respective props to determine whether the current ComposedComponent is selected or not by passing an onClick event handler all the way. Then in your sub component you can access tabIndex using this.props.tabIndex and render your parent div as
<div tabIndex={this.props.tabIndex}> </div>
The code below is almost like pseudo code to give an idea. If you feel that this does not solve your requirement you can try out a Tab example worked out by an awesome developer at this link CODEPEN EXAMPLE
const HOC = (ComposedComponent) => {
return class extends React.Component {
render (
<ComposedComponent
tabIndex={this.props.selected === this.props.index ? "0" : "-1"}
{...this.props}
/>
)
}
}
class Parent extends React.Component {
state = {
selected: 0
}
// Set the current selection based on the currentSelection argument
// that is bound to the function as it is sent along to Props
adjustTabIndices = (currentSelection) => (event) => {
this.setState({selection: currentSelection})
}
render {
return (
<div>
{
// These are your various MenuItem components that
// you want to compose using HOC
[MenuItem1, MenuItem2, MenuItem3].map(index => {
const MenuItem = HOC(MenuItem1);
return (
<MenuItem
key={index}
onClick={this.adjustTabIndices(index)}
selection={this.state.selected}
index={index}
/>
)
})
}
</div>
)
}
}

Resources