Is there anyway to detect the margin of a div? - reactjs

I want to know the margin-left of div1, so I can use it in my code. Setting it this way, the margin on the left will change depending on the screen size. How can I detect the margin left of this div?
<div class="max-w-6xl m-auto">
</div>

In react, you can make use of the ref callback, which receives the HTML DOM element as an argument. Inside of this callback, you could query the margin-left style from the element and store it in a state. Here's an example:
const App = () => {
const [marginLeft, setMarginLeft] = useState(0); // in pixels
const retrieveMargin = (elm) => {
if (elm != null) {
let styles = window.getComputedStyle(elm);
let ml = styles.getPropertyValue("margin-left");
setMarginLeft(Number.parseInt(ml.replace("px", "")));
}
};
return <div class="mydiv" ref={retrieveMargin}></div>;
};

Related

How to get the width of an element using ref in react

I'm trying to get the width of an element, and to do this I'm using the following...
export default () => {
const { sidebarOpen } = useContext(AuthContext)
const containerRef = useRef()
const getWidth = () => myRef.current.getBoundingClientRect().width
const [width, setWidth] = useState(0)
console.log(sidebarOpen)
useEffect(() => {
const handleResize = () => setWidth(getWidth())
if(myRef.current) setWidth(getWidth())
window.addEventListener("resize", handleResize)
console.log('abc', myRef.current.offsetWidth)
return () => window.removeEventListener("resize", handleResize)
}, [myRef, sidebarOpen])
console.log(width)
return (
<div ref={containerRef}>
...
</div>
)
}
When the width of the screen is changed in dev tools page resize, it works fine, but when the value of sidebar changes from 'true' to 'false' (or vice-versa). It returns the previous value of the container width. Does anyone know what I'm doing wrong, I need to get the most up to date value of the container width every time it changes, whether this is caused by a change to 'sidebarOpen' or not.
By default, a div element takes the full width of its parent.
I guess you're using flexbox for the sidebar, so when it closes, the element that contains the content (not the sidebar) expands to the full width of the available space.
This might be the issue that you're facing.
The div with flexbox (on the left we have the Sidebar component and on the right the content):
And right now, when there is no Sidebar:
As another example, let's create a div that has no other property rather than backgroundColor:
Eg:
<div style={{backgroundColor: 'red'}}>Hello</div>
And the result:

Get the ref of an element rendering by an array

I'm coding a tab navigation system with a sliding animation, the tabs are all visible, but only the selected tab is scrolled to. Problem is that, I need to get the ref of the current selected page, so I can set the overall height of the slide, because that page may be taller or shorter than other tabs.
import React, { MutableRefObject } from 'react';
import Props from './Props';
import styles from './Tabs.module.scss';
export default function Tabs(props: Props) {
const [currTab, setCurrTab] = React.useState(0);
const [tabsWidth, setTabsWidth] = React.useState(0);
const [currentTabHeight, setCurrentTabHeight] = React.useState(0);
const [currentTabElement, setCurrentTabElement] = React.useState<Element | null>(null);
const thisRef = React.useRef<HTMLDivElement>(null);
let currentTabRef = React.useRef<HTMLDivElement>(null);
let refList: MutableRefObject<HTMLDivElement>[] = [];
const calculateSizeData = () => {
if (thisRef.current && tabsWidth !== thisRef.current.offsetWidth) {
setTabsWidth(() => thisRef.current.clientWidth);
}
if (currentTabRef.current && currentTabHeight !== currentTabRef.current.offsetHeight) {
setCurrentTabHeight(() => currentTabRef.current.offsetHeight);
}
}
React.useEffect(() => {
calculateSizeData();
const resizeListener = new ResizeObserver(() => {
calculateSizeData();
});
resizeListener.observe(thisRef.current);
return () => {
resizeListener.disconnect();
}
}, []);
refList.length = 0;
return (
<div ref={thisRef} className={styles._}>
<div className={styles.tabs}>
{ props.tabs.map((tab, index) => {
return (
<button onClick={() => {
setCurrTab(index);
calculateSizeData();
}} className={currTab === index ? styles.tabsButtonActive : ''} key={`nav-${index}`}>
{ tab.label }
<svg>
<rect rx={2} width={'100%'} height={3} />
</svg>
</button>
)
}) }
</div>
<div style={{
height: currentTabHeight + 'px',
}} className={styles.content}>
<div style={{
right: `-${currTab * tabsWidth}px`,
}} className={styles.contentStream}>
{ [ ...props.tabs ].reverse().map((tab, index) => {
const ref = React.useRef<HTMLDivElement>(null);
refList.push(ref);
return (
<div ref={ref} style={{
width: tabsWidth + 'px',
}} key={`body-${index}`}>
{ tab.body }
</div>
);
}) }
</div>
</div>
</div>
);
}
This seems like a reasonable tab implementation for a beginner. It appears you're passing in content for the tabs via a prop named tabs and then keeping track of the active tab via useState() which is fair.
Without looking at the browser console, I believe that React doesn't like the way you are creating the array of refs. Reference semantics are pretty challenging, even for seasoned developers, so you shouldn't beat yourself up over this.
I found a good article that discusses how to keep track of refs to an array of elements, which I suggest you read.
Furthermore, I'll explain the differences between that article and your code. Your issues begin when you write let refList: MutableRefObject<HTMLDivElement>[] = []; According to the React hooks reference, ref objects created by React.useRef() are simply plain JavaScript objects that are persisted for the lifetime of the component. So what happens when we have an array of refs like you do here? Well actually, the contents of the array are irrelevant--it could be an array of strings for all we care. Because refList is not a ref object, it gets regenerated for every render.
What you want to do is write let refList = React.useRef([]), per the article, and then populate refList.current with refs to your child tabs as the article describes. Referring back to the React hooks reference, the object created by useRef() is a plain JavaScript object, and you can assign anything to current--not just DOM elements.
In summary, you want to create a ref of an array of refs, not an array of refs. Repeat that last sentence until it makes sense.

React, insert/append/render existing <HTMLCanvasElement>

In my <App> Context, I have a canvas element (#offScreen) that is already hooked in the requestAnimationFrame loop and appropriately drawing to that canvas, verified by .captureStream to a <video> element.
In my <Canvas> react component, I have the following code (which works, but seems clunky/not the best way to copy an offscreen canvas to the DOM):
NOTE: master is the data object for the <App> Context.
function Canvas({ master, ...rest } = {}) {
const canvasRef = useRef(master.canvas);
const draw = ctx => {
ctx.drawImage(master.canvas, 0, 0);
};
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
let animationFrameId;
const render = () => {
draw(ctx)
animationFrameId = window.requestAnimationFrame(render)
}
render();
return () => {
window.cancelAnimationFrame(animationFrameId);
}
}, [ draw ]);
return (
<canvas
ref={ canvasRef }
onMouseDown={ e => console.log(master, e) }
/>
);
};
Edited for clarity based on comments
In my attempts to render the master.canvas directly (e.g. return master.canvas; in <Canvas>), I get some variation of the error "Objects cannot be React children" or I get [object HTMLCanvasElement] verbatim on the screen.
It feels redundant to take the #offScreen canvas and repaint it each frame. Is there, instead, a way to insert or append #offScreen into <Canvas>, so that react is just directly utilizing #offScreen without having to repaint it into the react component canvas via the ref?
Specific Issue: Functionally, I'm rendering a canvas twice--once off screen and once in the react component. How do I (replace/append?) the component's <canvas> element with the offscreen canvas (#offScreen), instead of repainting it like I'm doing now?
For anyone interested, this was actually fairly straightforward, as I overcomplicated it substantially.
export function Canvas({ canvas, ...rest }) {
const container = useRef(null);
useEffect(() => {
container.current.innerHTML = "";
container.current.append(canvas);
}, [ container, canvas ]);
return (
<div ref={ container } />
)
}

Scroll to y location within react element with useRef

I have an component on my page that scrolls, but the rest doesn't. I want to scroll to a certain Y location within this element, when I change a useState value using a different component.
const scrollMe = useRef();
useEffect(
() => {
if (scrollMe === true) {
scrollPanel.scrollTo(0, 300)
}
},
[ scrollMe ]
);
The scrollable component is like so:
<div ref={scrollMe} onScroll={(e) => handleScroll(e)}>A mapped array</div>
The scroll function is as follows:
const handleScroll = (e) => {
if (e.target.scrollTop !== 0) {
setTopBarPosition(true);
} else {
setTopBarPosition(false);
}
};
Setting the topBarPosition affects
<div className={`topBar ${topBarPosition === true ? 'topBarToTop' : ''}`}>
<TopBar/>
</div>
And the css class topBarToTop moves the topBar to the top of the page:
.topBarToTop {
top: 30px;
}
How come I just get scrollMe.scrollTo is not a function ?
I made a codesandbox that illustrates what I'm trying to do:
https://codesandbox.io/s/react-hooks-counter-demo-izho9
You need to make some small changes, Please follow the code comments:
useEffect(() => {
// swap the conditions to check for childRef first
if (childRef && topPosition) {
// use to get the size (width, height) of an element and its position
// (x, y, top, left, right, bottom) relative to the viewport.
const { y } = childRef.current.getBoundingClientRect()
// You have to call `current` from the Ref
// Add the `scrollTo()` second parameter (which is left)
childRef.current.scrollTo(y, 0)
// Initially it was set to `false` and we change it to `true` when click on scroll
// button then we change it back to `false` when re-render
setTopPosition(false)
}
}, [topPosition, childRef])
More about getBoundingClientRect()

Correct way to pass useRef hook to ref property

I'm not sure how to formulate the question less vaguely, but it's about pass-by-value and pass-by-reference cases in react. And Hooks.
I am using gsap to animate a div slide-in and out is the context for this, but I'm going to guess that what the ref is used for shouldn't matter.
So, this works fine, even though this is a more class-component-typical way of passing a ref as i understand it:
const RootNavigation = () => {
var navbar = useRef();
const myTween = new TimelineLite({ paused: true });
const animate = () => {
myTween.to(navbar, 0.07, { x: "100" }).play();
};
return(
<div className="nav-main" ref={div => (navbar = div)}> // <<<<<<<<<< pass as a callback
...
</div>
)}
And this elicits a "TypeError: Cannot add property _gsap, object is not extensible" error, even though this is how the React Hooks guide would have me do it:
const RootNavigation = () => {
var navbar = useRef();
const myTween = new TimelineLite({ paused: true });
const animate = () => {
myTween.to(navbar, 0.07, { x: "100" }).play();
};
return(
<div className="nav-main" ref={navbar}> //<<<<<<<<<<<<< not passing a callback
...
</div>
)}
Could somebody explain to me what's going on here or even toss a boy a link to where it's already been explained? I'm sure some sort of Dan character has written about it somewhere, i'm just not sure what to google. Thank you!
In the first example you aren't using a ref, you are reassigning navbar through the ref callback so navbar is the DOM element.
It's the same as
let navbar = null;
return <div ref={node => (navbar = node)} />
In the second example you are using the ref object which is an object with a current property that holds the DOM element
const navbar = useRef(null)
return <div ref={navbar} />
navbar is now
{ current: the DOM element }
So you are passing the object into myTween.to() instead of the DOM element inside navbar.current
Now in the second example gsap is trying to extend the ref object itself and not the DOM element.
Why do we get the TypeError: Cannot add property _gsap, object is not extensible`?
If you look at the source code of useRef you will see on line 891
if (__DEV__) {
Object.seal(ref);
}
that React is sealing the ref object and JavaScript will throw an error when we try to extend it using Object.defineProperty() which is probably what gsap is doing.
The solution for using a ref will be to pass ref.current into tween.to()
const RootNavigation = () => {
const navbar = useRef()
const myTween = new TimelineLite({ paused: true });
const animate = () => {
myTween.to(navbar.current, 0.07, { x: "100" }).play()
}
return (
<div className="nav-main" ref={navbar}>
...
</div>
)
}

Resources