I have a Parent Component that is repeating a generic Child SVG component over the the screen.
In order to do so the Parent Component needs to know the dimensions of the child.
I used to store them in the child components like this (i know it's unusual but it worked)
function Child() {
return <path id="child" />
}
Child.width = 100
Child.height = 80
And then I used to read them on my parent component like this
function Parent(props) {
let width = props.children.type.width
let height = props.children.type.height
return Array.from(Array(props.times), (e, i) => {
return <Transform key={i} x={width*i} y={height*i}>
{props.children}
</Transform>
})
}
What I want is the Parent Container to calculate the dimensions of the child by itself so they're not static.
I know I need to access the dom of the child with a ref like this
function Parent(props) {
const test = React.useCallback(node => {
if (node !== null) {
const box = node.getBBox()
console.log(box) //this works
var width = box.width //this works too late
var height = box.height
}
}, []);
return Array.from(Array(props.times), (e, i) => {
return <Transform key={i} x={width*i} y={height*i}>
<g ref={test}> {props.children}</g>
</Transform>
})
}
But as I would have imagined React doesn't wait on my callback to execute the rest of the code, so it says that width and height are undefined.
How can I get the first element width and height once and then let the Parent component do his work? Maybe with Suspense? Any help is really appreciated thanks!
CodePudding user response:
First of all you can use svg as background image through url and place background repeat and sizing. But if you want this approach here we go. There are tons of ways to achieve this effect with forwardRef/refs. The code underneath is the first thing that came to mind I would suggest to have you svg as jsx/tsx file/react component - this way, otherwise you would need to provide parentRef into layoutEffect dependancies
import React, { useLayoutEffect, useRef } from 'react';
import logo from './logo.svg';
import './App.css';
import Logo from './Logo';
const Parent1: React.FC = ({ children }) => {
const parentRef = useRef<HTMLDivElement | null>(null);
useLayoutEffect(() => {
parentRef.current?.childNodes.forEach((node: any) =>
console.log({ height: node?.clientHeight, width: node?.clientWidth }),
);
}, []);
return (
<div className='parent1' ref={parentRef}>
{children}
</div>
);
};
function App() {
return (
<div className='App'>
<Parent1>
{Array(Math.ceil(Math.random() * 25 10))
/**
If you will decide to go img way - you need to place
parentRef as a dependancy for the layoutEffect
.fill(() => <img src={logo} alt='' />)
*/
.fill(Logo)
.map((E, i) => {
return <E key={i} />;
})}
</Parent1>
</div>
);
}
CodePudding user response:
So the solution was to update the state of the component inside the useEffect so our view gets updated when data is available:
function Parent(props) {
const [height, setHeight] = React.useState()
const [width, setWidth] = React.useState()
const test = React.useCallback(node => {
if (node !== null) {
const box = node.getBBox()
setWidth(box.width)
setHeight(box.height)
}
}, []);
return Array.from(Array(props.times), (e, i) => {
return <Transform key={i} x={width*i} y={height*i}>
<g ref={test}> {props.children}</g>
</Transform>
})
}