Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can a React component measure an unknown number of children individually, then modify layout based on height

I'm looking for a way to have a component render all of it's children, measure their heights, then modify the layout based on those heights. For example, say I have a Document component that contains several (and unknown quantity) Paragraph components. I want the Document component to be able to measure those Paragraphs and insert a horizontal rule where a page break would go.

Before layout:

<Document pageSize="A4" layout="portrait">
    <Paragraph text="some long text" />
    <Paragraph text="some long text" />
    <Paragraph text="some long text" />
    <Paragraph text="some long text" />
    <Paragraph text="some long text" />
    <Paragraph text="some long text" />
</Document>

After layout:

<Document pageSize="A4" layout="portrait">
    <Paragraph text="some long text" />
    <Paragraph text="some long text" />
    <hr /><!-- End of page 1 -->
    <Paragraph text="some long text" />
    <Paragraph text="some long text" />
    <Paragraph text="some long text" />
    <hr /><!-- End of page 2 -->
    <Paragraph text="some long text" />
</Document>

I can get each Paragraph component measure itself using the useClientRect() hook from the Hooks FAQ, but I don't know how to get the Document component to access that height. Any advice would be greatly appreciated!

I would also like to clarify that I'm looking to provide visual feedback on screen about where page breaks will be when printing, akin to the feedback provided by Google Docs or MS Word.

like image 751
Brent Parker Avatar asked Oct 16 '25 17:10

Brent Parker


1 Answers

This problem kept me up all night. But I think I found a solution that should work for you.

Essentially, you need to give each Paragraph component a ref. The ref will have info on the height on the entire component. You then just need to pass that height back-up to the Parent component that contains the Document component.

Upon receiving all the heights from the Paragraphs, you can calculate a runningHeight to dynamically add your PageBreaks. In this case, page-breaks are just <hr> tags that are conditionally added within each Paragraph component.

See working sandbox: https://codesandbox.io/s/dynamically-adding-pagebreaks-fjvfl

Main code:

App.js

import React from "react";
import ReactDOM from "react-dom";
import Document from "./Document";
import Paragraph from "./Paragraph";
import data from "./data.js";

import "./styles.css";

class App extends React.Component {
  state = {
    pageBreak: 500,
    heights: [],
    breaks: []
  };
  updateHeight = height => {
    this.setState(
      prevState => {
        return {
          heights: [...prevState.heights, height]
        };
      },
      () => {
        this.setBreaks();
      }
    );
  };

  setBreaks = () => {
    const { heights, pageBreak } = this.state;
    const breaks = [];
    let runningHeights = [];

    let runningHeight = 0;

    heights.forEach((height, index) => {
      if (index === 0) {
        runningHeight = height;
        runningHeights.push(runningHeight);
        if (runningHeight >= pageBreak) {
          breaks.push(true);
        } else {
          breaks.push(false);
        }
      } else if (index > 0) {
        if (runningHeights[index - 1] < pageBreak) {
          runningHeight = runningHeights[index - 1] + height;
          runningHeights.push(runningHeight);

          if (runningHeight >= pageBreak) {
            breaks.push(true);
          } else {
            breaks.push(false);
          }
        } else {
          runningHeight = height;
          runningHeights.push(runningHeight);
          if (runningHeight >= pageBreak) {
            breaks.push(true);
          } else {
            breaks.push(false);
          }
        }
      }
    });

    this.setState({
      breaks: breaks
    });
  };

  render() {
    const { breaks } = this.state;
    return (
      <div className="App">
        <Document>
          {data.map((item, index) => {
            return (
              <Paragraph
                text={item.text}
                updateHeight={this.updateHeight}
                break={breaks[index]}
              />
            );
          })}
        </Document>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Paragraph.js

import React, { useEffect } from "react";

const Paragraph = ({ text, updateHeight, ...props }) => {
  const paragraphRef = React.createRef();
  useEffect(() => {
    updateHeight(paragraphRef.current.offsetHeight);
  }, []);

  return (
    <div ref={paragraphRef}>
      {text} {props.break && <hr />}
    </div>
  );
};

export default Paragraph;

Document.js

import React from "react";

const Document = props => {
  return <div>{props.children}</div>;
};

export default Document;
like image 116
Chris Ngo Avatar answered Oct 18 '25 08:10

Chris Ngo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!