useChartDimensions

React Hook for Responsive D3 Charts

The useChartDimensions hook solves one of the most common challenges when building D3.js visualizations in React: creating responsive charts that properly size themselves based on their container. This hook calculates bounded dimensions for data visualizations, handles resize events automatically, and provides consistent margins. Perfect for creating D3.js charts that adapt to their container size.

This custom hook:

  • Provides a reference to attach to your chart container
  • Automatically tracks the container's dimensions as it resizes
  • Calculates the "bounded" drawing area by accounting for margins
  • Returns consistent dimensions for your visualization

Perfect for when you need your D3 charts to adapt to different screen sizes while maintaining proper spacing and margins.

import { useRef, useState, useEffect, useMemo } from 'react';
type ChartMargins = {
marginTop?: number;
marginRight?: number;
marginBottom?: number;
marginLeft?: number;
};
type ChartDimensions = {
width: number;
height: number;
boundedWidth?: number;
boundedHeight?: number;
} & ChartMargins;
const DEFAULT_MARGINS = {
marginTop: 40,
marginRight: 40,
marginBottom: 40,
marginLeft: 75,
};
const combineChartDimensions = (dimensions: ChartDimensions) => {
const parsedDimensions = {
...DEFAULT_MARGINS,
...dimensions,
};
return {
...parsedDimensions,
boundedHeight: Math.max(
parsedDimensions.height - parsedDimensions.marginTop - parsedDimensions.marginBottom,
0
),
boundedWidth: Math.max(parsedDimensions.width - parsedDimensions.marginLeft - parsedDimensions.marginRight, 0),
};
};
export const useChartDimensions = (passedSettings: ChartDimensions): [
React.RefObject<HTMLDivElement | null>,
Required<ChartDimensions>
] => {
const ref = useRef<HTMLDivElement>(null);
const [containerWidth, setContainerWidth] = useState(0);
const [containerHeight, setContainerHeight] = useState(0);
const dimensions = useMemo(() => combineChartDimensions({
...passedSettings,
width: passedSettings.width || containerWidth,
height: passedSettings.height || containerHeight,
}), [passedSettings, containerWidth, containerHeight]);
useEffect(() => {
if (dimensions.width && dimensions.height) return;
const element = ref.current;
if (!element) return;
const resizeObserver = new ResizeObserver((entries) => {
if (!Array.isArray(entries) || !entries.length) return;
const entry = entries[0];
if (containerWidth !== entry.contentRect.width) {
setContainerWidth(entry.contentRect.width);
}
if (containerHeight !== entry.contentRect.height) {
setContainerHeight(entry.contentRect.height);
}
});
resizeObserver.observe(element);
return () => {
resizeObserver.unobserve(element);
};
}, [passedSettings.width, passedSettings.height]);
return [ref, dimensions as Required<ChartDimensions>];
};