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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With