I try to set parent node background color like this, but does not work, why?
<div
onLoad="this.parentNode.style.background = 'yellow';"
>
Found here the inspiration: https://www.w3schools.com/jsref/prop_node_parentnode.asp
The main challange here is this: Make absolute positioned div expand parent div height
I have a parent div with position relative and a child div with position absolute, and I would set the parent height the same as the child.
This post above tells, it can be done only with Javascript, but there is no exact steps for it. Would you help? And I have React on top of it.
I thought if I can set color, I will able to set height also. Set color seemed a bit easier in first turn.
Why don't you use useRef hook to get the ref of the node?
const node = useRef(null);
...
<div ref={node} onLoad={() => {
node.current.parentNode.style.background = 'yellow';
}} />
Unless there's a really good reason to access the div directly, or as a ref, why not just use props?
Edit: I've included an example of how to use useRef to set the parent height using the child's calculated height.
import React, { useState, useEffect, useRef } from "react";
const ParentComponent = () => {
const [height, setHeight] = useState(null)
const node = useRef(null)
return (
<div
style={{
...(height ? { height: `${height}px` } : {}),
backgroundColor: 'yellow',
position: 'relative'
}}
>
<MyComponent setHeight={setHeight} node={node} />
</div>
)
}
const MyComponent = ({ setHeight, node }) => {
useEffect(() => {
const childHeight = node.current ? node.current.offsetHeight : 0
setHeight(childHeight), [node.current]
})
// sample parent updates when child updates
const [content, setContent] = useState(['child'])
useEffect(
() => setTimeout(() => setContent([...content, 'child']), 1000),
[content]
)
return (
<div style={{ position: 'absolute', top: 0, left: 0 }} ref={node}>
{content.map((item, i) => (
<div key={i}>{item + i}</div>
))}
</div>
)
}
Related
I would like to dynamically get the div element's width. With useRef, I can get the width of my div, but when I resize my page, the width value doesn't update automatically. Can you tell me what I need to do to make this happen?
Here's my code I put into Codesandbox.io, and an overview here:
import React from "react";
import "./styles.css";
const App = () => {
const ref = React.useRef(null);
const [width, setWidth] = React.useState(0);
React.useEffect(() => {
setWidth(ref.current.offsetWidth);
}, [width]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<div
style={{
border: "1px solid red"
}}
ref={ref}
>
Width: {width}
</div>
</div>
);
};
export default App;
Change your useEffect as below, so you add an event listener for when you resize the page. Also, notice I removed the width state from the dependency array:
useEffect(() => {
// when the component gets mounted
setWidth(ref.current.offsetWidth);
// to handle page resize
const getwidth = ()=>{
setWidth(ref.current.offsetWidth);
}
window.addEventListener("resize", getwidth);
// remove the event listener before the component gets unmounted
return ()=>window.removeEventListener("resize", getwidth)
}, []);
I want to get a ref, more specifically a getBoundingClientRect() on the <Header/> and <Testimonials/> component. I then want to watch for a scroll event and check if the two components ever overlap. Currently, my overlap variable never flips to true even if what appears on the page is that the two components are overlaping.
const [isIntersecting, setIsIntersecting] = useState(false)
const header = useRef(null)
const testimonials = useRef(null)
const scrollHandler = _ => {
let headerRect = header.current.getBoundingClientRect();
let testiRect = testimonials.current.getBoundingClientRect();
let overlap = !(headerRect.right < testiRect.left ||
headerRect.left > testiRect.right ||
headerRect.bottom < testiRect.top ||
headerRect.top > testiRect.bottom)
console.log(overlap) // never flips to true
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
const App = () => {
return (
<div className="App">
<Header />
<LandingPage />
<div style={{ height: '100vh', backgroundColor: 'black', color: 'white' }}>
</div>
<AboutPage />
<TestimonialsPage />
<Footer />
</div>
);
}
First: Components can't receive directly a ref prop, unless you are wrapping the Component itself in a React.forwardRef wrapper:
const Component = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// Inside your Parent Component:
const ref = useRef();
<Component ref={ref}>Click me!</Component>;
Second: you can also pass a ref down to a child as a standard prop, but you can't call that prop ref since that's a special reserved word just like the key prop:
const Component= (props) => (
<button ref={props.myRef}>
{props.children}
</button>
);
// Inside your Parent Component
const ref = useRef();
<Component myRef={ref}>Click me!</Component>;
This works perfectly fine, and if it's a your personal project you
might work like this with no issues, the only downside is that you
have to use custom prop name for those refs, so the code gets harder to
read and to mantain, especially if it's a shared repo.
Third: Now that you learnt how to gain access to the DOM node of a child Component from its parent, you must know that even if usually it's safe to perform manipulations on those nodes inside a useEffect ( or a componentDidMount ) since they are executed once the DOM has rendered, to be 100% sure you will have access to the right DOM node it's always better using a callback as a ref like this:
const handleRef = (node) => {
if (node) //do something with node
};
<Component ref={handleRef}/>
Basically your function hanldeRef will be called by React during
DOM node render by passing the node itself as its first parameter,
this way you can perform a safe check on the node, and be sure it's
100% valorized when you are going to perform your DOM manipulation.
Concerning your specific question about how to access the getBoundingClientRect of a child Component DOM node, I made a working example with both the approaches:
https://stackblitz.com/edit/react-pqujuz
You'll need to define each of your components as Forwarding Refs, eg
const Header = forwardRef<HTMLElement>((_, ref) => (
<header ref={ref}>
<h1>I am the header</h1>
</header>
));
You can then pass a HTMLElement ref to your components to refer to later
const headerRef = useRef<HTMLElement>(null);
const scrollHandler = () => {
console.log("header position", headerRef.current?.getBoundingClientRect());
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, []);
return (
<Header ref={headerRef} />
);
I'm using TypeScript examples since it's easier to translate back down to JS than it is to go up to TS
I would like to "synchronize" the x-position of 2 "divs" within a react component. Eventually, I would like to have a table header that stays always visible and a table that can scroll vertically. The horizontal offset of that table header and that table are supposed to remain "in sync".
The "onScroll" event fires. However, changing the "state" of the property "offsetX" in my function reactToScrolling has no effect on my "divs" (as far as I can see). What can I do to make this work?
const { useState } = require('react');
const MainComponent = () => {
const [ offsetX, setOffsetX ] = useState(0);
function reactToScrolling(e) {
console.log(e.target.scrollLeft);
setOffsetX(e.target.scrollLeft);
}
return (
<>
<div style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={reactToScrolling}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'red' }} scrollLeft={offsetX}>
...
</div>
</div>
<div style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={reactToScrolling}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'blue' }} scrollLeft={offsetX}>
...
</div>
</div>
</>
)
};
export default MainComponent;
Eventually, I found the solution myself. It works perfectly if useRef is used instead of useState. When the scroll event fires, the scrollLeft property of div1 is set to the value of the scrollLeft property of div2 using the references created with useRef.
const { useRef } = require('react');
const MainComponent = () => {
const div1 = useRef(null);
const div2 = useRef(null);
const onScroll = () => {
div1.current.scrollLeft = div2.current.scrollLeft;
}
return (
<>
<div ref={div1} style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={onScroll}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'lightgray' }}>
...
</div>
</div>
<div ref={div2} style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={onScroll}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'lightgray' }}>
...
</div>
</div>
</>
)
};
export default MainComponent;
Thanks, this helped me a lot.
For a final touch, I just added another function for the top div so that it can handle the scrolling itself too.
const onScrollTop = () => {
div2.current.scrollLeft = div1.current.scrollLeft;
}
I am trying to pass state from parent to child using React, however both components are imported and therefor the state variables of the parent component are not declared.
I have two components both exported from the same file. The first component is a wrapper for the second. This component has a useEffect function which find its height and width and set these values to hook state.
export const TooltipWrapper = ({ children, ariaLabel, ...props }) => {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current && ref.current.getBoundingClientRect().width) {
setWidth(ref.current.getBoundingClientRect().width);
}
if (ref.current && ref.current.getBoundingClientRect().height) {
setHeight(ref.current.getBoundingClientRect().height);
}
});
return <TooltipDiv>{children}</TooltipDiv>;
The next component which is exported from the same file looks like this
export const Tooltip = ({
ariaLabel,
icon,
iconDescription,
text,
modifiers,
wrapperWidth,
}) => {
return (
<TooltipContainer
aria-label={ariaLabel}
width={wrapperWidth}
>
<TooltipArrow data-testid="tooltip-arrow" modifiers={modifiers} />
<TooltipLabel
aria-label={ariaLabel}
>
{text}
</TooltipLabel>
</TooltipContainer>
);
};
The component Tooltip is expecting a prop wrapperWidth. This is where I want to pass in the width hook value from the TooltipWrapper component.
Both components are imported into my App component
import React from "react";
import { GlobalStyle } from "./pattern-library/utils";
import { Tooltip, TooltipWrapper } from "./pattern-library/components/";
function App() {
return (
<div className="App">
<div style={{ padding: "2rem", position: "relative" }}>
<TooltipWrapper>
<button style={{ position: "relative" }}>click </button>
<Tooltip
modifiers={["right"]}
text="changing width"
wrapperWidth={width}
/>
</TooltipWrapper>
</div>
</div>
);
}
Here I am told that width is not defined, which I expect since I'm not declaring width in this file.
Does anyone have an idea of how I can access the width and height state value for the parent component within the App file?
Render Props could work:
Add a renderTooltip prop to <TooltipWrapper>:
<TooltipWrapper renderTooltip={({ width }) => <Tooltip ...existing wrapperWidth={width} />}>
<button style={{ position: 'relative' }}>click</button>
</TooltipWrapper>
NB. ...existing is just the other props you are using with Tooltip
And then update the return of <TooltipWrapper>:
return (
<TooltipDiv>
{children}
props.renderTooltip({ width });
</TooltipDiv>
);
I am making this class called Scrollable which enables scrolling if the width/height of the children elements exceeds a certain value. Here is the code.
import React, { Component } from 'react';
const INITIAL = 'initial';
class Scrollable extends Component {
render() {
let outter_styles = {
overflowX: (this.props.x? 'auto': INITIAL),
overflowY: (this.props.y? 'auto': INITIAL),
maxWidth: this.props.width || INITIAL,
maxHeight: this.props.height || INITIAL,
};
return (
<div ref={el => this.outterEl = el} style={outter_styles}>
<div ref={el => this.innerEl = el}>
{this.props.children}
</div>
</div>
);
}
};
export default Scrollable;
// To use: <Scrollable y><OtherComponent /></Scrollable>
This works great. Except now I wish to add one more functionality which makes the scrollable always scroll to the bottom. I have some idea of how to do it:
this.outterEl.scrollTop = this.innerEl.offsetHeight;
But this only need to be called when this.props.children height changes. Is there any idea on how to achieve this?
Thanks in advance.
I would recommend a package element-resize-detector. It is not React-specific but you can easily build a high-order component around it or integrate your Scrollable component with it.
Now I have an idea of solving this.
Since I am using react-redux. The problem is that I could not use lifecycle hooks on this Scrollable component since this.props.children might not necessarily be changed when the content is updated.
One way to achieve this is to make Scroll component aware of the corresponding values in the redux state. So that when that relevant value is updated, we can scroll down to the bottom.
Scrollable component:
import React, { Component } from 'react';
const INITIAL = 'initial';
class Scrollable extends Component {
componentWillUpdate(){
if(this.props.autoScroll){
// only auto scroll when the scroll is already at bottom.
this.autoScroll = this.outterEl.scrollHeight - this.outterEl.scrollTop - Number.parseInt(this.props.height) < 1;
}
}
componentDidUpdate(){
if(this.autoScroll) this.outterEl.scrollTop = this.outterEl.scrollHeight;
}
render() {
let styles = {
overflowX: (this.props.x? 'auto': INITIAL),
overflowY: (this.props.y? 'auto': INITIAL),
maxWidth: this.props.width || INITIAL,
maxHeight: this.props.height || INITIAL,
};
return (
<div ref={el => this.outterEl = el} style={styles}>
<div ref={el => this.innerEl = el}>
{this.props.children}
</div>
</div>
);
}
};
export default Scrollable;
Scrollable container:
import { connect } from 'react-redux';
import Scrollable from '../components/Scrollable';
const mapStateToProps = (state, ownProps) => Object.assign({
state: state[ownProps.autoScroll] || false
}, ownProps);
export default connect(mapStateToProps)(Scrollable)
With this, Scrollable's life cycle hooks will be called when the corresponding state changes.