Get component's height every time it renders - reactjs

So hey guys, basically I'm using react and I want to get the parent div's height, and make it's child to have the same height, by props. The parent div renders every time the window is resized. I tried using componentDidMount and setState to get the height of the parent, but componentDidMount is called only the first time my parent div renders.
And I can't use ReactDOM.findDOMNode(this).clientHeight inside render()function.
To simplify, these are the steps:
(Everytime) Window is resized
Div1 gets rendered
Gets Div1 height and set it state
Pass it by props to Div2.
Any ideas?
Here's a piece of code:
import React, { Component } from 'react';
import Div2 from './Div2';
class Div1 extends Component {
constructor(props){
super(props);
this.state = {
height: 0
};
}
componentDidMount() {
var height = (ReactDOM.findDOMNode(this).clientHeight);
this.setState({height: height})
}
render() {
return(
<div className='Div1'>
<Div2 height={this.state.height}/>
</div>
);
}
}
export default Div1;

There are 3 places you have to update your parent's state with new height at:
componentDidMount which will be called after the first render (first time parent's div will actually appear).
componentDidUpdate which is called after render-ing caused by props and state updates. You have to do only if you are actually using any props and their update can result in div's height change.
window resize.
You have to use refs to get parent div's DOM element inside render method. After that you cat use it in componentDidMount and componentDidUpdate (please, check React Component Lifecycle docs).
Combining everything together results in following code, where Foo passes it's root div height to Bar:
class Bar extends React.Component {
render() {
return (
<div className='bar' style={{height: `${this.props.height / 2 }px`}} />
);
};
};
class Foo extends React.Component {
constructor() {
super();
this.state = { height: 0 };
this.updateHeight = this.updateHeight.bind(this);
}
componentDidMount() {
this.updateHeight();
window.addEventListener("resize", this.updateHeight);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateHeight);
}
componentDidUpdate() {
this.updateHeight();
}
updateHeight() {
if (this.state.height != this.div.clientHeight)
this.setState({ height: this.div.clientHeight })
}
render() {
return (
<div ref={ div => { this.div = div; } } className='foo'>
<Bar height={this.state.height} />
</div>
);
}
}
ReactDOM.render(<Foo/>, document.getElementById('app'));
Working example could be found here.

Related

How can I import element to another React component?

I've got 2 components and want to get Panel_Menu element in another child component to do some stuff with it.
class Panel extends Component {
constructor(props){
super(props);
this.menuRef = React.createRef();
}
componentDidMount() {
console.log (this.menuRef.current)
// works correctly
}
render() {
return(
<>
<Panel_Menu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current}/>
</Panel_Menu>
</>
)
}
}
class Menu extends Component {
constructor(props) {
super(props);
}
isSame = () => {
const isSlideClass = this.props.item;
console.log(isSlideClass)
// is null
// expected output: → <div class="panel-menu"></div>
}
render() {
return (
<Left_Menu >
<Panel_Menu_Items className="test" onClick={this.isSame} />
</Left_Menu>
);
}
}
How can I update data in done render() to reach my goal?
Or... how can I get element instantly in external Component (Menu in this case) to do some stuff with it?
Issue
The issue here is that React refs, when attached on the initial render, will be undefined during the initial render. This means that item={this.menuRef.current} will enclose the initial undefined ref value in the click handler of the child.
Solution
It's simple, you really just need to trigger a rerender to reenclose an updated React ref value. You can either add some state to the Panel component and update it in the componentDidMount lifecycle method, or just issue a forced update.
class Panel extends Component {
menuRef = createRef();
componentDidMount() {
console.log(this.menuRef.current);
this.forceUpdate(); // <-- trigger rerender manually
}
render() {
return (
<>
<PanelMenu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current} />
</PanelMenu>
</>
);
}
}
Demo

Do react render props cause remounting of the child components?

I was just wondering if people know if using the "render props" pattern causes excessive mounting/unmounting of the child component.
For example, adapting from the react docs (https://reactjs.org/docs/render-props.html):
<Mouse>
{mouse => (
<ShowMousePosition mouse={mouse}/>
)}
</Mouse>
class ShowMousePosition extends React.Component {
componentDidMount(){
console.log('mounting!')
}
render () {
const {mouse} = this.props
return (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)
}
}
I know the react docs say:
Using a render prop can negate the advantage that comes from using React.PureComponent if you create the function inside a render method. This is because the shallow prop comparison will always return false for new props, and each render in this case will generate a new value for the render prop.
But, will "mounting!" be called over and over as the user moves the mouse around?
Thanks!
I went ahead and tried to answer my own question using a fiddle. It appears that "mounting!" is not called over and over again:
https://jsfiddle.net/69z2wepo/186690/
Here is the code:
class Hello extends React.Component {
render() {
return <Mouse>
{mouse => (
<ShowMousePosition mouse={mouse}/>
)}
</Mouse>
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: 800, width: 800 }} onMouseMove={this.handleMouseMove}>
{/*
Instead of providing a static representation of what <Mouse> renders,
use the `render` prop to dynamically determine what to render.
*/}
{this.props.children(this.state)}
</div>
);
}
}
class ShowMousePosition extends React.Component {
componentDidMount(){
console.log('mountin!')
}
render () {
const {mouse} = this.props
return (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
componentDidMount is only called once but componentDidUpdate will be called multiple times along with your render function every time your state/props is changed.

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.

ReactJS: How can I change dynamically inserted Component's data

I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>

React.js - setState after calculation based on props

I have a component that receives images as props, performs some calculation on them, and as a result I need to update its class. But if I use setState after the calculation, I get the warning that I shouldn't update state yet... How should I restructure this?
class MyImageGallery extends React.Component {
//[Other React Code]
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
//NEED TO UPDATE STATE HERE?
}
//componentWillUpdate()? componentDidUpdate()? componentWillMount()? componentDidMount()? {
//I CAN ADD CLASS HERE USING REF, BUT THEN THE COMPONENT'S
// FIRST RENDERED WITHOUT THE CLASS AND IT'S ONLY ADDED LATER
}
render() {
return (
<div ref="galleryWrapper" className={GET FROM STATE????}
<ImageGallery
items={this.getImages()}
/>
</div>
);
} }
You should put your logic into componentWillReceiveProps (https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops) so as to do a prop transition before render occurs.
In the end what we did was run the logic in the constructor and then put the class into the initial state:
class MyImageGallery extends React.Component {
constructor(props) {
super(props);
this.getImages = this.getImages.bind(this);
this.images = this.getImages();
this.state = {smallImgsWidthClass: this.smallImgsWidthClass};
}
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
this.smallImgsWidthClass = '[calculation result]';
return this.props.images;
}
render() {
return (
<div className={this.state.smallImgsWidthClass }
<ImageGallery
items={this.images}
/>
</div>
);
}
}

Resources