简体   繁体   中英

How to get parent width/height in React using Hooks?

I'm creating a component and I need to get it's parent <div> width and height. I'm using Hooks, so all my components are functions. I've read some examples using classes, but this won't apply to my component.

So I have this component:

export default function PlantationMap(props) {
    <div className="stage-canvas">
        <Stage
          width={window.innerWidth * 0.5}
          height={window.innerHeight * 0.5}
          onWheel={handleWheel}
          scaleX={stage.stageScale}
          scaleY={stage.stageScale}
          x={stage.stageX}
          y={stage.stageY}
          draggable
        / >
    </div>
}

How could I get the <div> height and width to use in <Stage width={} height={} /> ?

Thank you very much in advance

Edit: I tried using the useRef() hook, like this:

const div = useRef();

return (
  <div ref={div}>
  ...
  </div>
)

But I can't access the div.current object

I think useCallback is what you want to use so you can get the width and height when it changes.

  const [height, setHeight] = useState(null);
  const [width, setWidth] = useState(null);
  const div = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
      setWidth(node.getBoundingClientRect().width);
    }
  }, []);

  return (
    <div ref={div}>
    ...
    </div>
  )

Declare a reference using useRef hook and then read current.offsetHeight and current.offsetWidth properties.

Here is the code:

import React, { useEffect, useRef } from 'react';

const PlantationMap = (props) => {

    const stageCanvasRef = useRef(null);

    // useEffect will run on stageCanvasRef value assignment
    useEffect( () => {

        // The 'current' property contains info of the reference:
        // align, title, ... , width, height, etc.
        if(stageCanvasRef.current){

            let height = stageCanvasRef.current.offsetHeight;
            let width  = stageCanvasRef.current.offsetWidth;
        }

    }, [stageCanvasRef]);

    return(
        <div className = "stage-canvas" ref = {stageCanvasRef}>
            <Stage
              width={window.innerWidth * 0.5}
              height={window.innerHeight * 0.5}
              onWheel={handleWheel}
              scaleX={stage.stageScale}
              scaleY={stage.stageScale}
              x={stage.stageX}
              y={stage.stageY}
              draggable
            / >
        </div>);

}

export default PlantationMap;

to my knowledge if it is concerned with style can only be registered by:

<Stage style={{width:window.innerWidth * 0.5,height:width:window.innerWidth * 0.5}} />

You can make use of the built-inResizeObserver :

export default function PlantationMap(props) {
    const [width, setWidth] = useState(100);
    const [height, setHeight] = useState(100);

    useEffect(() => {
        const resizeObserver = new ResizeObserver((event) => {
            // Depending on the layout, you may need to swap inlineSize with blockSize
            // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize
            setWidth(event[0].contentBoxSize[0].inlineSize);
            setHeight(event[0].contentBoxSize[0].blockSize);
        });

        resizeObserver.observe(document.getElementById("div1"));
    });

    return (
        <div id="div1" className="stage-canvas">
            <Stage
                width={width * 0.5}
                height={height * 0.5}
                onWheel={handleWheel}
                scaleX={stage.stageScale}
                scaleY={stage.stageScale}
                x={stage.stageX}
                y={stage.stageY}
                draggable
            / >
        </div>
    );
}

I think ResizeObserver is the way to go as mentioned in the answer from Dan. I just wouldn't use the document.getElementById . Either use useMeasure from react-use or create everything on your own.

There are two scenarios:

  1. Component contains the container that you'd like to observe
  2. Component is a child component and doesn't have the container reference

To 1 - Reference directly accessible

In this case, you can create the reference with useRef in the component and use it at resizeObserver.observe(demoRef.current) .

import "./styles.css";
import React, { useEffect, useRef, useState } from "react";

const DisplaySize = ({ width, height }) => (
  <div className="centered">
    <h1>
      {width.toFixed(0)}x{height.toFixed(0)}
    </h1>
  </div>
);

const Demo = () => {
  const [width, setWidth] = useState(100);
  const [height, setHeight] = useState(100);
  const demoRef = useRef();

  useEffect(() => {
    const resizeObserver = new ResizeObserver((event) => {
      // Depending on the layout, you may need to swap inlineSize with blockSize
      // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize
      setWidth(event[0].contentBoxSize[0].inlineSize);
      setHeight(event[0].contentBoxSize[0].blockSize);
    });

    if (demoRef) {
      resizeObserver.observe(demoRef.current);
    }
  }, [demoRef]);

  return (
    <div ref={demoRef} className="App">
      <DisplaySize width={width} height={height} />
    </div>
  );
}; //);

export default function App() {
  return <Demo />;
}

To 2 - Reference of container not directly accessible:

This case is probably happening more often and requires slightly more code. You need to pass the reference from the parent to the child component with React.forwardRef .

Demo code can be found below or in the following Codesandbox

Some words to the code:

  • In the parent component you create a reference with const containerRef = useRef() and use it at the main container with <div ref={containerRef}/> . Under the hood it will do something like ref => containerRef.current=ref
  • Next, pass the reference to the Demo component.

Why not use React.createRef ?

That would work too but it would recreate the reference on every render of your App. Please have a look here for an explanation of the difference between useRef and createRef .

In short, use useRef with functional components and use createRef with class-based components.

 const {useEffect, useRef, useState} = React; const DisplaySize = ({ width, height }) => ( <div className="centered"> <h1> {width.toFixed(0)}x{height.toFixed(0)} </h1> </div> ); const Demo = React.forwardRef((props, ref) => { const [width, setWidth] = useState(100); const [height, setHeight] = useState(100); useEffect(() => { const resizeObserver = new ResizeObserver((event) => { // Depending on the layout, you may need to swap inlineSize with blockSize // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize setWidth(event[0].contentBoxSize[0].inlineSize); setHeight(event[0].contentBoxSize[0].blockSize); }); if (ref && ref.current) { resizeObserver.observe(ref.current); } }, [ref]); return <DisplaySize width={width} height={height} />; }); function App() { const containerRef = useRef(); return ( <div ref={containerRef} className="App"> <Demo ref={containerRef} /> </div> ); } const rootElement = document.getElementById("root"); ReactDOM.render( <App />, rootElement );
 /* apply a natural box layout model to all elements, but allowing components to change */ html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } html, body { margin: 0; padding: 0; }.App { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; font-family: sans-serif; text-align: center; border: 4px solid red; }.centered { display: flex; /* establish flex container */ flex-direction: column; /* make main axis vertical */ justify-content: center; /* center items vertically, in this case */ align-items: center; /* center items horizontally, in this case */ height: 100%; }
 <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <div id="root"></div>

Library React-use

There are also some useful hooks in React-use that could help here.

useWindowSize and useSize look pretty similar but after looking at the source code the first one relies on the window.onresize event and requires less code to implement.

useSize will add an iframe below the current component ( z-index: -1 ) to track the size with resize event and requires more code. It also adds a little debounce with setTimeout .

So use useWindowSize if you just need the width/height to do some calculations on the first render and useSize if you'd like to show that the size changed.

useWindowSize

If you just need to get the window size useWindowSize is the way to go. They're doing it with onresize event with document.addEventlistener('resize', resizeHandler) and checking innerWidth / innerHeight Codesandbox Demo

useMeasure

To track an element size, useMeasure can be used. It is using ResizeObserver under the hood, so it's like the code above where ref is the reference you'd like to track:

The first element returned by useMeasure is the setRef method. So you can do the following in your component:

const [setRef, { width, height }] = useMeasure();
useEffect(() => {
    setRef(ref.current)
}, [])

Please have a look at the following Codesandbox .

useSize

If you want to track the size of a component useSize could be used as mentioned in the docs . Codesandbox Demo useSize

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM