I want to create npm module for react. In this module i need to know height and width of components that passed into my component as props.children. I don't have control over that children. Currently i do that
{React.cloneElement(this.props.children, { ref: this.myRef })}
inside render and then in other function i can get width and height this way
let myRect = this.myRef.current.getBoundingClientRect();
if (!this.state.childSize.height && myRect.height !== 0) {
var cSize = { height: myRect.height, width: myRect.width };
}
but it only works if children is passed like a dom element
<MyModuleComponent>
<div>Hello world!</div>
</MyModuleComponent>
and didn't work if children is passed as a react component
<MyModuleComponent>
<SomeChildComponent/>
</MyModuleComponent>
in this case this.myRef.current doesn't have getBoundingClientRect function.
How can i get width and height of children in this case?
Related
I'm using the react-scrollbar package to render a scrollbar for my my content. What I also want is a arrow button that, on click, moves to a certain scrollbar area. The problem is, I'm trying to style (marginTop) a class inside my component.
This is my attempt:
// MY COMPONENT
scrollToNextUpload = () => {
const NextUpload = 400
this.setState({ marginTop : this.state.marginTop + NextUpload }, () => document.getElementsByClassName('scrollarea-content')[0].style.marginTop = "'" + this.state.marginTop + "px'")
}
// MY RENDER
render () {
<ScrollArea>
// my content
<div onClick={this.scrollToNext}></div>
</ScrollArea>
}
What is actually rendered
<div class='scrollarea'>
<div class='scrollarea-content'>
// my content
<div onClick={this.scrollToNext}></div>
</div>
</div>
What I want
To make my area with the scrollbar scroll, I have to add a marginTop style to the 'scrollarea-content'. I could do this by passing props to the < ScrollArea > and then use them inside the installed package; but I'm trying to avoid altering the original package content.Also, is there another way how I could scroll by click and is there someone else who's experienced with that NPM Package?
Most libraries give props to apply style to child components, in this library you can pass a className to the contentClassName or use inline style in contentStyle prop :
<ScrollArea contentStyle={{ marginTop: 10 }}>
An another way is to write css to add style to the scrollarea-content class.
In a .css file :
.scrollarea-content {
margin-top: 10px;
}
Edit: In your case you can programatically change the marginTop style by using the props like this :
scrollToNextUpload = () => {
const NextUpload = 400;
this.setState(prevState => ({ marginTop : prevState.marginTop + NextUpload }));
}
render () {
<ScrollArea contentStyle={{ marginTop: this.state.marginTop }}>
// my content
<div onClick={this.scrollToNext}></div>
</ScrollArea>
}
Note the use of a functional setState to prevent inconsistencies when next state value depends on the previous state.
I've created a React component which takes any component and renders it as a Pop-up. A parent component receives the component to be rendered (popped up). The rendered component is here the child component which using react-sizeme to get its size and pass back to parent component. The parent component must take the dimensions of child component, so adjusts' its height and width. This is the code:
class Popup extends React.Component<IPopupProps,IComponent>{
constructor(props:IPopupProps){
super(props);
this.state={
childComponent:this.props.children,
style:{
height:0,
width:0
}
}
}
// This function runs two times, before and after rendering child component
// & so have an improper visualization as the size is changed twice here
public OnSize = (size:any) =>{
const width = size.width +20;
const height =size.height+20;
this.setState({
style:{height,
width }
})
}
public render(){
return(
<div className='popup'>
<div style={this.state.style} className='popup-content'>
<a className="close" onClick={this.props.onExit}>
×
</a>
<this.state.childComponent onSize={this.OnSize}/>
</div>
</div>
)
}
}
The initial width and height is set to 0. So it doesn't renders properly. So is there any way so that to hide the child component or avoid its rendering before parent component gets the size?
EDIT: We can't get the size until the child component is rendered. So is there any trick to get this done. Just a component needs to be popped-up properly.
EDIT 2: Here's the PropsBuilder.tsx which calls the Popup.tsx and sends the component to display as children
class PopupBuilder extends React.Component<IPopupBuilderProps, IPopup>{
constructor(props:IPopupBuilderProps){
super(props);
this.state = {
showPopup:false
}
}
public togglePopup = () =>{
this.setState({
showPopup:!this.state.showPopup
})
}
public render (){
return(
<React.Fragment>
<button onClick={this.togglePopup}>{this.props.trigger}</button>
<React.Fragment>
{this.state.showPopup?<Popup onExit={this.togglePopup} >{this.props.component}</Popup>:null}
</React.Fragment>
</React.Fragment>
)
}
}
export default PopupBuilder;
Actually, this looks like more general DOM/JavaScript question.
Consider such case:
const span = document.createElement('span');
span.innerText = 'hello';
span.getBoundingClientRect() // -> { width: 0, height: 0, top: 0, … }
This is an indicator that you don't know the dimensions of the element until it is in DOM (Rendered in react);
document.body.appendChild(span);
span.getBoundingClientRect(); // -> {width: 50, height: 16, …}
My recommendation to you in this case are:
Child component should accept a property (function) from Parent one
Use React "ref" feature to find actual dimensions of Child element
Call the function in 'componentDidMount' (use componentDidUpdate if child can change dynamically), passing it child component dimensions.
If you don't have access to child component. You may wrap it like this:
// Popup.tsx
class Popup .... {
...
render() {
<Measurer>{this.props.children}</Measurer>
}
}
and implement the logic of fetching dimensions in it. Measurer is a direct child of Popup and their communication can be controlled by you.
I have an animated component that slides up/down depending on the prop (true or false). I'm using maxHeight: 0 to hide the component (transition is being done with CSS) and that's the default state that's being passed as prop. For the opened style I use a maxHeight much bigger than needed just to make sure the content will fit properly. After it's opened I'm able to get its height by ref and set the maxHeight accordingly.
export default class AnimatedInput extends React.Component {
constructor (props) {
super(props);
this.state = {
height: 600
}
}
componentDidUpdate(prevProps) {
var height = this.refs.inputNode ? this.refs.inputNode.clientHeight : height;
console.log(height);
if (this.props.open === false && prevProps.open === true) {
this.setState({height: height});
}
}
render () {
var {height} = this.state;
let test = this.props.open ? 'boxVisible' : 'boxHidden';
var styles = {
boxHidden: {
...
maxHeight: 0,
},
boxVisible: {
....
maxHeight: height,
}
}
return (
<div style={styles[test]} ref="inputNode">
{this.props.children}
</div>
)
}
}
There are 2 problems with this approach:
The first time it's opened and closed is not smooth due to maxHeight being larger than it should (maybe render the opened one off-screen and get its height first?)
If it's closed before fully opened the height will be lower than it should (I suppose it's an easy fix - just need to stop updating the height value).
Am I on the right track? How would you fix these? Should I stick to CSS or maybe make the transition entirely in JS. Thanks for your suggestions!
You're looking for ReactCSSTransitionGroup. I used this for the exact same thing you are.
I have a grid of components with 3 per row.
They are divs which represent a product and have inner components such as price and description. These products sometimes have longer titles which push the other components downward.
This is fine, but when it happens I want the titles for the other components in the same row to have a height the same, so that the next components (price, rating) are vertically aligned. So the price for each product in a row will be the same.
Then, I want to make the height of the row the max height of the three elements in the row.
Is there a good way I can manipulate the height of elements dynamically which will work with react?
I would inject a function in all child components which is called after the child component is rendered.
Example:
var Product = React.createClass({
componentDidMount: function() {
var height = 200; // calculate height of rendered component here!
// set height only one time
if (this.props.findHeight) {
this.props.setHeight(height);
}
},
render: function() {
return <div style={{height:this.props.height,backgroundColor:'green'}}>
Product
</div>;
}
});
var Main = React.createClass({
getInitialState: function() {
return {
maxHeight:0,
findHeight: true
}
},
componentDidMount: function() {
this.setState({
maxHeight: this.state.maxHeight,
findHeight: false
});
},
setMaxHeight: function(maxHeight) {
if (maxHeight > this.state.maxHeight) {
this.setState({maxHeight: maxHeight})
}
},
render: function() {
return (<div>
<Product setHeight={this.setMaxHeight} height={this.state.maxHeight} findHeight={this.state.findHeight} />
</div>);
}
});
The logic how to calculate the actual height of a component is a different question. You can solve it e.g. with jQuery (How to get height of <div> in px dimension). Unfortunately I can not answer the question for vanilla js, but I am sure that you find the answer very fast when you ask google :-)
Greetings
By extending the answer provided by CapCa, you can get the actual height of the component by Element.getBoundingClientRect().
let domRect = element.getBoundingClientRect();
You can also target the top element of your targeted react component by using React Ref. Your code could be like this,
let domRect = this.TargetedElementRef.getBoundingClientRect(),
elementHeight = domRect.height,
elementWidth = domRect.width; // you can get the width also
I have a parent React component that contains a child React component.
<div>
<div>Child</div>
</div>
I need to apply styles to the child component to position it within its parent, but its position depends on the size of the parent.
render() {
const styles = {
position: 'absolute',
top: top(), // computed based on child and parent's height
left: left() // computed based on child and parent's width
};
return <div style={styles}>Child</div>;
}
I can't use percentage values here, because the top and left positions are functions of the child and parent's widths and heights.
What is the React way to accomplish this?
The answer to this question is to use a ref as described on Refs to Components.
The underlying problem is that the DOM node (and its parent DOM node) is needed to properly position the element, but it's not available until after the first render. From the article linked above:
Performing DOM measurements almost always requires reaching out to a "native" component and accessing its underlying DOM node using a ref. Refs are one of the only practical ways of doing this reliably.
Here is the solution:
getInitialState() {
return {
styles: {
top: 0,
left: 0
}
};
},
componentDidMount() {
this.setState({
styles: {
// Note: computeTopWith and computeLeftWith are placeholders. You
// need to provide their implementation.
top: computeTopWith(this.refs.child),
left: computeLeftWith(this.refs.child)
}
})
},
render() {
return <div ref="child" style={this.state.styles}>Child</div>;
}
This will properly position the element immediately after the first render. If you also need to reposition the element after a change to props, then make the state change in componentWillReceiveProps(nextProps).
This is how I did it
const parentRef = useRef(null)
const handleMouseOver = e => {
const parent = parentRef.current.getBoundingClientRect()
const rect = e.target.getBoundingClientRect()
const width = rect.width
const position = rect.left - parent.left
console.log(`width: ${width}, position: ${position}`)
}
<div ref={parentRef}>
{[...Array(4)].map((_, i) => <a key={i} onMouseOver={handleMouseOver}>{`Item #${i + 1}`}</a>)}
</div>
The right way to do this is to use CSS. If you apply position:relative to the parent element then the child element can be moved using top and left in relation to that parent. You can even use percentages, like top:50%, which utilizes the height of the parent element.