简体   繁体   中英

How to get element height and width from ReactNode?

I have a dynamic component in which I pass in children as prop. So the props look something like:

interface Props {
   ...some props
   children: React.ReactNode
}

export default Layout({...some props, children}: Props) {...}

I need to access the size of the children elements (height and width), in the Layout component. Note that the children are from completely different components and are non-related .

I can use the Layout component as follow:

<Layout ...some props>
  <Child1 /> // I need to know the height and width of this child
  <Child2 /> // as well as this child
  <Child3 /> // and this child.
</Layout>

How can I do so dynamically? Do I somehow have to convert ReactNode to HTMLDivElement ? Note that there is no way I can pass in an array of refs as a prop into Layout . Because that the pages which use Layout are dynamically generated .

You can achieve this by using React.Children to dynamically build up a list of references before rendering the children. If you have access to the children element references, you can follow the below approach. If you don't then you can follow the bit at the bottom.

You have access to the children element references

If the children components pass up their element reference, you can use React.Children to loop through each child and get each element reference. Then use this to perform calculations before the children components are rendered.

ie This is a very simple example on how to retrieve the references and use them. This wouldn't be performant if there were multiple components added/removed as it stores it in a set and doesn't remove components.

const Layout = ({ children }) => {
  const references = useRef(new Set());

  function getReference(ref) {
    references.current.add(ref);
  }

  const clonedChildren = React.Children.map(children, (child) => {
    return React.cloneElement(child, {
       ref: getReference
    });
  });

  const heights = React.useMemo(() => {
    return Array.from(references.current).map((ref) => ref.getBoundingClientRect());
  }, [references.current]);

  return clonedChildren;
}

If you don't have access to the children element references

If the children components aren't passing up an element as the reference, you'll have to wrap the dynamic children components in a component so we can get an element reference. ie

const WrappedComponent = React.forwardRef((props, ref) => {
   return (
     <div ref={ref}>
       {props.children}
     </div>
   )
});

When rendering the children components, then the code above that gets the references will work:

<Layout>
  <WrappedComponent>
    <Child1 />
  </WrappedComponent>
</Layout>

Since we don't know how your children is built, here is what I can propose you :

import React from 'react';
import { render } from 'react-dom';

const App = () => {
  const el1Ref = React.useRef();
  const el2Ref = React.useRef();

  const [childrenValues, setChildrenValues] = React.useState([]);

  React.useEffect(() => {
    setChildrenValues([
      el1Ref.current.getBoundingClientRect(),
      el2Ref.current.getBoundingClientRect()
    ]);
  }, []);
  return (
    <Parent childrenVals={childrenValues}>
      <span ref={el1Ref}>
        <Child value="Hello" />
      </span>
      <span ref={el2Ref}>
        <Child value="<div>Hello<br />World</div>" />
      </span>
    </Parent>
  );
};

const Parent = ({ children, childrenVals }) => {
  React.useEffect(() => {
    console.log('children values from parent = ', childrenVals);
  });
  return <>{children}</>;
};

const Child = ({ value }) => {
  return <div dangerouslySetInnerHTML={{ __html: value }} />;
};

render(<App />, document.getElementById('root'));

And here is the repro on Stackblitz .

The idea is to manipulate how your children is built.

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