getting components height before the mounting in React js - reactjs

I need the way to get known of the my highest component height before rendering the table in ReactJS.
there are a lot of rows and cells, so I created virtualization for my table.
The problem is the following, I cut only visible in the table viewport rows and cells. But my cells have dynamic data from backend. So during scroling they expand and shrink.
Is the way to get the height before mounting? or rendering each cell in hidden component and cached the height?

If you set the style of your row to use a state variable, you can then set this variable on CompomentDidMount. Something like the below.
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
minHeight: 0,
};
}
componentDidMount() {
// Or however you get height from a ref
const fooHeight = this.elementFoo.clientHeight;
const barHeight = this.elementBar.clientHeight;
const minHeight = fooHeight > barHeight ? fooHeight : barHeight;
this.setState({ minHeight });
}
render() {
const { minHeight} = this.state;
return (
<div style={{ 'min-height': `${minHeight}px` }} ref={elm => (this.elementFoo = elm)}>Foo</div>
<div style={{ 'min-height': `${minHeight}px` }} ref={elm => (this.elementBar = elm)}>Bar</div>
}
}

If you require a virtualized table I would recommend not reinventing the wheel and going for something like react-virtualized.
As for just getting the rendered dimensions of a component, react-virtualized also has an HOC that you can use called CellMeasurer.

Related

How to create a simple spinning animation for an image in React

I'm using reactjs with material-ui styling, to create a dapp and have an image (circular in shape) which i want to spin continuously which customizable spin speed, maintaining a smooth flow. The spin speed needs to be customizable in which i feed a speed value to the component and it'd spin in that speed accordingly. Any ideas how to go by? Thanks.
PS: this is not related to 'loading components', loading animation, or loading image in any way. also, a solution which can be implemented using withStyles() of material-ui would be preferred. Thanks.
I'm writing this answer with respect to my comments above:
First, define a css animation keyframe to do a spin:
#keyframes spin {
from {transform:rotate(0deg);}
to {transform:rotate(360deg);}
}
Next, in your constructor, define the speed value:
constructor(props) {
super(props);
this.state = {
speed: 3
}
}
finally, make use of inline styling in ReactJS to pass a custom speed value from state (or sth like this.props.customSpdProps if you want to pass from props):
<img style={{animation: `spin ${this.state.speed}s linear infinite`}} src={SampleImg} alt="img"/>
Control spin speed by setting an initial property then propagate it to a react hook to be dynamically changed.
const SPEED = 0;
const kind = React.createElement,
container = "react-container";
const tags = {
cat : document.querySelector('[data-cat]'),
burger : document.querySelector('[data-burger]')
}
class Main extends React.Component {
constructor(props){
super(props)
}
componentDidMount() {
alert("say cheeze!")
}
render() {
const kinds = [];
Object.keys(tags).map(function(key, index) {
const targets = {
key : index,
tag : tags[key].tagName.toLowerCase(),
src : tags[key].src,
SPEED
}
kinds.push(kind(Spin, targets))
});
return kinds
}
}
const Spin = function(props) {
const [speed, go] = React.useState(props.SPEED);
const startSpin = function() {
go(speed + 1)
};
React.useEffect(function() {
startSpin()
}, []);
return kind(props.tag, { style : { animation : `spin ${speed}s linear infinite` }, src : props.src })
}
Demo https://gif.com.ai?id=QmagKQ16ZhwPMGunhWuiKydEJFW3y4MprxYeTNjbC87MxZ
From: https://dev.to/egfx/thinking-in-gif-with-react-5am0

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.

How to create toggleable sidenav layout in React.js?

I am porting my layout from jQuery to React.js. This is very common one that consists of:
header with toggle button
sidenav with navigation links
content whose width adapts to sidenav state.
As you can imagine to achieve that a lot of (css) stuff is going on. I am really confused about possible approaches.
Here is mine:
class CoreLayout extends Component {
constructor(props) {
super(props)
this.state = {
sidenavCollapsed: false
}
}
onSidenavToggle() {
const { sidenavCollapsed } = this.state
document.body.classList.toggle('collapsed', !sidenavCollapsed)
this.setState({ sidenavCollapsed: !sidenavCollapsed })
}
render() {
const { sidenavCollapsed } = this.state
return (
<div>
<Header onSidenavToggle={::this.onSidenavToggle}></Header
<Sidenav>
<div className="content">content</div>
</div>
)
}
}
I do all the styling according to class attached to body element:
.collapsed .header {}
.collapsed .sidenav {}
.collapsed .content {}
Basically it's toggling sidenav width and content margin betwen 220 and 60.
So...
Should I pass collapsed property to each of layout elements and add class collapsed separately? What I am trying to achieve is similar to this.
What is the correct way of doing fade-out-in sidenav items animation? Till now I was using jQuery utilities, but I am not sure if directly using window.requestAnimationFrame() is correct. I have tried ReactCSSTransitionGroup with no success.
Just add a class to the navbar on button toggle and animate the transition using css.
See the demo
https://jsfiddle.net/kuLy0g8z/

ReactJS displaying / hiding parallel components

Preface: I'm very new to ReactJS, so my question will reflect my lack of understanding of how things work. Right now I'm using React + Alt.
I have a basic page with a parent component (I'll call it MyContainer) that has multiple parallel panel-esque components (MyPanel) displayed side by side. What I'm now trying to implement is each panel component has an Expand icon that when clicked, will make that panel take up the full width of its parent container (ie. width:100%) and hide all other panels (ie: display:none or visibility:hidden). When the button is clicked again, the initial state of all panels is again shown.
So my question is the proper way to implement this in ReactJS/Alt. So far I have the easy part done, which is listening for the Expand button click and updating the state of whether the panel should be displayed as normal or full-width.
export default class MyPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false
};
this.handleExpand = this.handleExpand.bind(this);
}
handleExpand() {
this.setState({ expanded: !this.state.expanded });
}
render() {
var expandStyle = {};
if (this.state.expanded) {
expandStyle.width = 'calc(100% - 40px)';
}
return(
<div style={expandStyle}>
<ExpandButton onExpandEvent={this.handleExpand} />
{/* Rest of panel props data rendered here */}
</div>
);
}
}
The part I don't know how to do is have this component tell its sibling components whether they should hide or show itself. Trying to find similar questions here, I think there are two approaches: using props in the parent component to keep track of which panels are hidden or shown, or using Store/Actions to keep track of this, but I'm not sure on the implementation of either.
Again, I'm new to ReactJS, so go easy on me :)
You have your methodology for how to expand/hide slightly off: since your panels are all encompassed within a parent component, that component should decide if an individual panel is normal, expanded, or hidden. You should be using the state of the parent component to accomplish this, rather than the state of each panel component. For example:
var Container = React.createClass({
getInitialState () {
return {
expandedPanel: null,
panels: someArrayOfPanelData
};
},
changeExpandedPanel (panelIndex) {
return () => {
this.setState({
expandedPanel: panelIndex
});
};
},
render () {
let panels = this.state.panels.map((panelData, i) => {
return (
<Panel
key={i}
data={panelData}
expanded={(i == this.state.panelExpanded)}
handleExpansion={this.changeExpandedPanel(i)}
/>
);
});
return (
<div>
{panels}
</div>
);
}
});
var Panel = React.createClass({
propTypes: {
...,
handleExpansion: React.PropTypes.function.isRequired
},
handleExpand () {
this.props.handleExpansion();
},
render () {
...
}
});
What you are doing here is passing the parent's event handler down to the children. Since the function is defined in the parent, it has access to the parent's state/props, etc.

ReactJS get rendered component height

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 });
}
}

Resources