import React, { useLayoutEffect } from "react";
import { Box } from "@chakra-ui/react";
import { useBoundingRect } from "../hooks";
export default function Item({ children, setItemPosition, itemWidth, gap }) {
const [ref, { x }] = useBoundingRect();
useLayoutEffect(() => setItemPosition(Math.round(x)), [x]);
return (
<Box
ref={ref}
w={`${itemWidth}px`}
_notLast={{
mr: `${gap}px`
}}
flex="0 0 auto"
>
{children}
</Box>
);
}
The above component is mapped by a parent component. It sits horizontally in a carousel I am building.
ref is assigned to the dom element and a hook (see below for hook) attaches a resize event listener to use .getBoundingClientRect() to return the elements x position value.
The x value is then passed to the parent state in setItemPosition() which has been passed down in props.
Secondary to this, itemWidth is passed down in props and assigned to the DOM element, giving it its width. This happens AFTER the x value has already been calculated which means that the x positions are out of date and have recorded a value minus the width of the previous element in the carousel.
How can I record the x value AFTER the DOM element has been assigned its width?
I have tried:
• Tried using useLayoutEffect to assign the width and then useEffect to pass the x position but it didn't work.
• Reversing how the itemWidth is calculated and instead of calculating it in the parent and passing it down, passing down from the parent only the value it uses to calculates from and doing the calculation in the child and passing it up.
The hook
import { useState, useCallback, useLayoutEffect } from "react";
import { debounce } from "../utils";
function getDimensionObject(node) {
const rect = node.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: rect.top,
left: rect.left,
x: rect.x,
y: rect.y,
right: rect.right,
bottom: rect.bottom
};
}
export default function useBoundingRect(limit) {
const [dimensions, setDimensions] = useState({});
const [node, setNode] = useState(null);
const ref = useCallback((node) => setNode(node), []);
useLayoutEffect(() => {
if ("undefined" !== typeof window && node) {
const measure = () =>
window.requestAnimationFrame(() =>
setDimensions(getDimensionObject(node))
);
measure();
const listener = debounce(limit ? limit : 250, measure);
window.addEventListener("resize", listener);
window.addEventListener("scroll", listener);
return () => {
window.removeEventListener("resize", listener);
window.removeEventListener("scroll", listener);
};
}
}, [node, limit]);
return [ref, dimensions, node];
}