\n \n );\n};\n\r\n// box component\nfunction Box() {\n const boxRef = React.useRef(null);\n \r\n return (\n
0px(w) x 0px(h)
\nI don't know the width or height of this box. It expands to fill your screen. If you're on a desktop, it might be pretty big. If you're on a mobile, it will be smaller.
But what if I needed to know the width and height?
Well, it turns out I do need to know the width and height because I need to display it in the middle of the box! It says 0px(w) x 0px(h)
right now, which is wrong! So let's fix it.
ref
with the useRef()
hookTo find out the width and height of the box, we will need to access the DOM node after React has rendered it on the screen. We can useRef()
for this.
After React creates the DOM node and puts it on the screen, React will set the current property of your ref object to that DOM node.
First, we declare the ref object:
const boxRef = useRef(null);
And then we add the ref
attribute to our div
.
<div ref={boxRef}>
Here's our Box
component looks with the boxRef
:
// box component
function Box() {
const boxRef = React.useRef(null);
return (
<div ref={boxRef} className="...">
<p className="...">0px(w) x 0px(h)</p>
</div>
);
};
Now that we have a ref
assigned to our element with the useRef
hook, we can access the DOM node. And that means that we can get the width using the native JavaScript method getBoundingClientRect()
.
Since we want to render the value on the page, I'll add a width state with an initial value of 0.
const [width, setWidth] = React.useState(0);
Once the component has been rendered, we will useEffect
to check the width of the element, and update the state with setWidth()
.
React.useEffect(() => {
setWidth(boxRef.current.getBoundingClientRect().width)
}, [])
And we will use that width state in our dimensions instead of 0: {width}px(w) x 0px(h)
.
Here's how the component looks now:
// box component
function Box() {
const boxRef = React.useRef(null);
const [width, setWidth] = React.useState(0);
React.useEffect(() => {
setWidth(boxRef.current.getBoundingClientRect().width)
}, [])
return (
<div ref={boxRef} className="...">
<p className="...">{width}px(w) x 0px(h)</p>
</div>
);
};
On load, we get the width of the element. It doesn't stay updated if you resize the window - we will fix that later. But before that, let's get the height, and fix a little bug I hadn't noticed yet!
Now that we know how to get the width, you might have a good idea of how to get the height. And you are probably right!
We're going to use pretty much the same code we used to get the width, but instead of getBoundingClientRect().width
we need getBoundingClientRect().height
.
We will also add a height
state and render that instead of 0!
// box component
function Box() {
const boxRef = React.useRef(null);
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
React.useEffect(() => {
let { width, height } = boxRef.current.getBoundingClientRect();
setWidth(width);
setHeight(height);
}, []);
return (
<div ref={boxRef} className="...">
<p className="...">{width}px(w) x {height}px(h)</p>
</div>
);
};
setTimeout()
to run after initial renderSomething I noticed after getting the height was that sometimes, in fact, most of the time, the height was wrong.
It turns out, useEffect()
may run before the browser paints the updated screen. In this case, it meant the CSS making the box full width and height of the parent container wasn't always getting picked up at the time useEffect()
checks.
React may run your Effect before the browser paints the updated screen. This ensures that the result of the Effect can be observed by the event system. Usually, this works as expected. However, if you must defer the work until after paint, such as an
alert()
, you can usesetTimeout
. -- React -useEffect
caveats
By adding a setTimeout
we can get the width and height after render to make sure they are correct.
React.useEffect(() => {
setTimeout(() => {
let { width, height } = boxRef.current.getBoundingClientRect();
setWidth(width);
setHeight(height);
}, 0);
}, []);
Now our width and height match what is displayed after the CSS has been applied!
The element size might change after the initial render. For example, maybe your element width and/or height might change if the browser window is resized.
Right now, the original dimensions will remain displayed until you refresh the page because our useEffect()
only runs on the initial render.
And useRef()
doesn't trigger a re-render of your component if the element changes.
What you need to do instead is figure out what could cause the width or height of your element to change, and check for the updated values then. For this example, we can add an event listener to the useEffect()
to check for the "resize" event.
React.useEffect(() => {
// on initial render
setTimeout(() => {
updateSize();
}, 0);
// gets the size and updates state
function updateSize() {
let { width, height } = boxRef.current.getBoundingClientRect();
setWidth(width);
setHeight(height);
};
// event listener
window.addEventListener("resize", updateSize);
// remove the event listener before the component gets unmounted
return () => window.removeEventListener("resize", updateSize);
}, []);
These events can happen pretty quickly. When you resize the window that getWidth
function could run hundreds of times within seconds.
If in response to getting the width and/or, you're doing something in the DOM (like changing another element to match), you might want to run it through a debounce function so that you only make the changes once when the user has finished resizing. Or you could use a throttle function to reduce the function calls during the resize.
Here's the updated code with my useDebounce
custom hook: