Skip to content

Instantly share code, notes, and snippets.

@alettieri
Created March 10, 2023 03:28
Show Gist options
  • Save alettieri/ca5907fb6f2d529837265645b1d8721f to your computer and use it in GitHub Desktop.
Save alettieri/ca5907fb6f2d529837265645b1d8721f to your computer and use it in GitHub Desktop.
Recharts Responsive Container Solution
import React from 'react';
import { Box } from '@mui/material';
import { useElementDimensions } from '../../hooks/useElementDimensions';
const ResponsiveChartContainer = ({
children,
}: {
children: React.ReactElement;
}) => {
const { componentRef, dimensions } = useElementDimensions();
const chartContent = React.useMemo(() => {
if (dimensions === null) {
return children;
}
// Recharts needs a height and width property to determine if it has valid chart dimensions to deal with
// https://github.com/recharts/recharts/blob/a6ed9c43b7c2d6b2ecb2a21ae3753a100eebf312/src/chart/generateCategoricalChart.tsx
return React.cloneElement(children, {
height: dimensions?.height,
width: dimensions?.width,
});
}, [children, dimensions]);
return (
<Box
ref={componentRef}
data-test="ResponsiveChartContainer"
position="relative"
width="100%"
height="100%"
>
<Box position="absolute">{chartContent}</Box>
</Box>
);
};
export { ResponsiveChartContainer };
import React from 'react';
import { useElementResize } from './useElementResize';
/**
* Use this hook to get back a DOMRect of the element.
* The hook is designed to re calculate the DOMRect should it be resized or the child
* structure changes
*/
function useElementDimensions<T extends HTMLElement = HTMLDivElement>() {
const [dimensions, updateDimensions] = React.useState<null | DOMRect>(null);
const { componentRef } = useElementResize<T>({
onResize(el) {
updateDimensions(el.getBoundingClientRect());
},
});
return { dimensions, componentRef };
}
export { useElementDimensions };
import React from 'react';
/**
* Watches element for resize events and calls the callback
* Ideally we use this hook as little as possible given that it requires watching DOM based resizes
* We should try to use as many css responsive options as possible instead
*/
function useElementResize<T extends HTMLElement = HTMLDivElement>(options: {
onResize: (el: T) => void;
}) {
const ref = React.useRef<T>(null);
const onResizeCallback = React.useRef(options.onResize);
const animationFrameId =
React.useRef<ReturnType<typeof window.requestAnimationFrame>>(null);
const handleCallback = React.useCallback(() => {
window.cancelAnimationFrame(animationFrameId.current);
animationFrameId.current = window.requestAnimationFrame(() => {
if (ref.current) {
onResizeCallback.current(ref.current);
}
});
}, []);
const componentRef = React.useCallback(
(node: T) => {
ref.current = node;
handleCallback();
},
[handleCallback]
);
React.useLayoutEffect(() => {
let handleResize = () => {
handleCallback();
};
let resizeObserver: ResizeObserver;
let changeObserver: MutationObserver;
if (ref.current) {
resizeObserver = new ResizeObserver(handleResize);
resizeObserver.observe(ref.current);
changeObserver = new MutationObserver(handleResize);
changeObserver.observe(ref.current, {
childList: true,
});
}
return () => {
resizeObserver?.disconnect();
changeObserver?.disconnect();
handleResize = () => undefined;
window.cancelAnimationFrame(animationFrameId.current);
};
}, [handleCallback]);
React.useEffect(() => {
onResizeCallback.current = options.onResize;
}, [options.onResize]);
return {
componentRef,
};
}
export { useElementResize };
@alettieri
Copy link
Author

alettieri commented Mar 10, 2023

The Box component can be replaced with a div if @mui/material isn't something you're using in your project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment