Reputation: 656
I have a component that returns an array of refs when mapping the {children}
import { useRef } from "react";
const children = ["hey", "now", "heeeey", "now"];
const Test = () => {
const itemRefs = useRef([]);
const setItemRef = (element, index) => {
itemRefs.current[index] = element;
};
console.log(itemRefs)
//current: Array(4)
//0: <div>hey</div>
//1: <div>now</div>
//2: <div>heeeey</div>
//3: <div>now</div>
return (
<>
{children.map((child, index) => {
return (
<div ref={(element) => setItemRef(element, index)} key={index}>
{child}
</div>
);
})}
</>
);
};
export default Test;
I have a hook that tracks the dimensions of a supplied ref.
export default function useBoundingRect(ref, limit) {
const [boundingClient, setBoundingClient] = useState({
x: 0,
y: 0,
width: 0,
height: 0
});
useEffect(() => {
if (typeof window !== "undefined" && ref.current) {
const set = () => {
const { x, y, width, height } = ref.current.getBoundingClientRect();
setBoundingClient({ x, y, width, height });
};
set();
const listener = debounce(limit ? limit : 100, set);
window.addEventListener("resize", listener);
return () => window.removeEventListener("resize", listener);
}
}, [ref, limit]);
return boundingClient;
}
itemRefs
?Do I pass the entire object into the hook and modify the hook to return an array of dimensions?
Upvotes: 0
Views: 511
Reputation: 42218
One of the fundamental rules of refs is that you must call the same amount of refs with each render. This means that we can't call useBoundingRect
in a loop because the length of children
might change.
To get around this, we can create an element ChildWrapper
that goes around each child. This element calls the hook and communicates with the parent.
const ChildWrapper = ({children, setRect}) => {
const ref = useRef(null);
const rect = useBoundingRect(ref);
useEffect(() => {
setRect(rect);
}, [rect]);
return <div ref={ref}>{children}</div>;
};
const Test = () => {
// an array of rectangle dimensions
const [rects, setRects] = useState([]);
// helper to update a rect by index
const setRect = (index) => (rect) => {
// I am using slice instead of map to support changes in array length
setRects((prev) => [
...prev.slice(0, index),
rect,
...prev.slice(index + 1)
]);
};
console.log("rects", rects);
return (
<>
{children.map((child, index) => {
return (
<ChildWrapper key={index} setRect={setRect(index)}>
{child}
</ChildWrapper>
);
})}
</>
);
};
Upvotes: 1