How to position a React component relative to its parent? - javascript

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.

Related

React How to get width and height of props.chidren component?

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?

How do I style a div inside a component without passing props to that component (I'm using a package)

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.

How to change React parent component size based on child component size without rendering child component?

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.

How to detect hidden component in React

In brief,
I have a infinite scroll list who render for each Item 5 PureComponent.
My idea is to somehow, only render the 5 PureComponent if the Item is visible.
The question is,
How to detect if the Item component is visible for the user or not?
Easiest solution:
add scrollPosition and containerSize to this.state
create ref to container in render()
<div ref={cont => { this.scrollContainer = cont; }} />
in componentDidMount() subscribe to scroll event
this.scrollContainer.addEventListener('scroll', this.handleScroll)
in componentWillUnmount() unsubscribe
this.scrollContainer.removeEventListener('scroll', this.handleScroll)
your handleScroll should look sth like
handleScroll (e) {
const { target: { scrollTop, clientHeight } } = e;
this.setState(state => ({...state, scrollPosition: scrollTop, containerSize: clientHeight}))
}
and then in your render function just check which element should be displayed and render correct ones numOfElementsToRender = state.containerSize / elementSize and firstElementIndex = state.scrollPosition / elementSize - 1
when you have all this just render your list of elements and apply filter base on element's index or however you want to sort them
Ofc you need to handle all edge cases and add bufor for smooth scrolling (20% of height should be fine)
You can use the IntersectionObserver API with a polyfill (it's chrome 61+) . It's a more performant way (in new browsers) to look for intersections, and in other cases, it falls back to piro's answer. They also let you specify a threshold at which the intersection becomes true. Check this out:
https://github.com/researchgate/react-intersection-observer
import React from 'react';
import 'intersection-observer'; // optional polyfill
import Observer from '#researchgate/react-intersection-observer';
class ExampleComponent extends React.Component {
handleIntersection(event) {
console.log(event.isIntersecting); // true if it gets cut off
}
render() {
const options = {
onChange: this.handleIntersection,
root: "#scrolling-container",
rootMargin: "0% 0% -25%"
};
return (
<div id="scrolling-container" style={{ overflow: 'scroll', height: 100 }}>
<Observer {...options}>
<div>
I am the target element
</div>
</Observer>
</div>
);
}
}

How can I resize elements dynamically while using react?

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

Categories