4

In Google Docs, if you create a document. You will start writing on Page 1. If the page is filled with content, then automatically Page 2 is created and the rest of the content goes to page 2 and so on. If you remove some lines from Page 1, then the contents from Page 2 (If there are any) will be moved to the bottom of Page 1.

So in my scenario, I have some presentational components (Around 10) which get filled by a form. Consider Left Pane has a form and updating them shows a live preview of Docs-like UI on the right pane.

I tried directly manipulating DOM using some methods like insertBefore in a react app. I read that doing DOM manipulation directly on a React App is error-prone and it is not recommended. Is there any other pattern or method to achieve this use case?

isherwood
  • 58,414
  • 16
  • 114
  • 157
Sivanesh S
  • 1,117
  • 10
  • 16

2 Answers2

3

I'd say that the only to solve your problem would be to measure the size of content with useLayoutEffect, and then to 'split' said content into pages.

(This is because I'm assuming you don't know the size of the each item from the list - if you did it would be simpler!).

I'd start with a useComponentSize hook that would look something like so:


// Adapted from: https://stackoverflow.com/a/57272554/6595024


const useComponentSize = () => {
  const informParentOfHeightChange = useContext<(h: number) => void>(
    UpdateParentAboutMyHeight
  );
  const targetRef = useRef();

  useLayoutEffect(() => {
    if (targetRef.current) {
      informParentOfHeightChange(targetRef.current.offsetHeight);
    }

    return () => informParentOfHeightChange(0);
  }, [informParentOfHeightChange]);

  return targetRef;
};


const UpdateParentAboutMyHeight = /* Left as an exercise to the reader */

Note: That my implementation is rather 'basic'; you'd probably want to useTransition or some other type of debounce effect so your UI wouldn't slow down to a crawl

Using this hook, the child would be able to inform the parent whenever it's height changed. Here's an example:

const Comp = () => {
  const targetRef = useComponentSize();

  return <div ref={targetRef}>{/* etc */}</div>;
};

So then 'all' you'd need to do is create a parent component that keeps track of the height of all of it's Comp children.

Here's some (inefficient) pseudo code to help you get an idea:


const UpdateParentAboutMyHeightProvider = /* Left as an exercise to the reader */

const HEIGHT_PER_PAGE = 1000;

const PageLayout = ({ items_to_render }: { items_to_render: Comp[] }) => {
  const [items, setItems] = useState<{ height: number }[]>([]);

  let curHeight = 0;
  let curPage = 0;
  const pages = items_to_render.reduce((pageList, item, index) => {
    const itemHeight = items[index].height;
    if (curHeight + itemHeight >= HEIGHT_PER_PAGE) {
      // Create a new page!
      pageList.push([item]);
      curHeight = itemHeight;
      curPage += 1;
      return pageList;
    }
    // Add to existing page!
    curHeight += itemHeight;
    pageList[curPage].push(item);
    return pageList;
  }, [] as Array<Array<Comp>>);

  return (
    <UpdateParentAboutMyHeightProvider value={setItems}>
      {pages.map((page, number) => (
        <Page number={number + 1} key={number}>{page.map((item) => item)}</Page>
      ))}
    </UpdateParentAboutMyHeightProvider>
  );
};

I'll repeat that the implementation I wrote here is very basic and inefficient; the logic for when a page would be bigger than the maximum size is also probably insufficient[1]

Hopefully it'll be helpful in pointing you in the right direction.

[1] I kept it simple for readability and because I don't know your particular use case

Joaquim Esteves
  • 400
  • 2
  • 12
0

As mentioned earlier (by Syed), you can use Draft.js. But if you want a simple and flexible pagination component that works with any data source, why not using react-paginate.

To use react-paginate, you need to install it with npm:

npm install react-paginate

Then you need to import it in your React component:

import ReactPaginate from 'react-paginate';

See the example below in codesandbox on "How to use react-paginate".

Ali Safari
  • 1,535
  • 10
  • 19