Merge pull request #318 from unslothai/fix/recharts-dimension-warning

Fix Recharts -1 dimension warning on chart mount
This commit is contained in:
Roland Tannous 2026-03-08 03:34:58 +04:00 committed by GitHub
commit ff56a5f785
2 changed files with 109 additions and 37 deletions

View file

@ -46,22 +46,94 @@ function ChartContainer({
}) {
const uniqueId = React.useId();
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
const containerRef = React.useRef<HTMLDivElement | null>(null);
const [containerSize, setContainerSize] = React.useState<{
width: number;
height: number;
} | null>(null);
React.useEffect(() => {
const element = containerRef.current;
if (!element) return;
const updateSizeState = () => {
const { width, height } = element.getBoundingClientRect();
const nextSize =
width > 0 && height > 0
? {
width: Math.round(width),
height: Math.round(height),
}
: null;
setContainerSize((currentSize) => {
if (!nextSize) {
// Keep the last valid size once mounted to avoid unmount/remount thrash.
return currentSize;
}
if (
currentSize &&
currentSize.width === nextSize.width &&
currentSize.height === nextSize.height
) {
return currentSize;
}
return nextSize;
});
};
updateSizeState();
if (typeof ResizeObserver === "undefined") {
const recheckSize = () => {
if (document.visibilityState === "visible") {
updateSizeState();
}
};
window.addEventListener("resize", recheckSize);
window.addEventListener("orientationchange", recheckSize);
document.addEventListener("visibilitychange", recheckSize);
return () => {
window.removeEventListener("resize", recheckSize);
window.removeEventListener("orientationchange", recheckSize);
document.removeEventListener("visibilitychange", recheckSize);
};
}
const observer = new ResizeObserver(() => {
updateSizeState();
});
observer.observe(element);
return () => observer.disconnect();
}, []);
return (
<ChartContext.Provider value={{ config }}>
<div
ref={containerRef}
data-slot="chart"
data-chart={chartId}
className={cn(
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex min-w-0 aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className,
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
{containerSize ? (
<RechartsPrimitive.ResponsiveContainer
width="100%"
height="100%"
minWidth={0}
minHeight={1}
initialDimension={containerSize}
>
{children}
</RechartsPrimitive.ResponsiveContainer>
) : null}
</div>
</ChartContext.Provider>
);
@ -100,30 +172,30 @@ ${colorConfig
);
};
const ChartTooltip = RechartsPrimitive.Tooltip;
function ChartTooltipContent({
active,
payload,
className,
const ChartTooltip = RechartsPrimitive.Tooltip;
function ChartTooltipContent({
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
}: Partial<RechartsPrimitive.TooltipContentProps<any, any>> &
React.ComponentProps<"div"> & {
hideLabel?: boolean;
hideIndicator?: boolean;
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
}) {
formatter,
color,
nameKey,
labelKey,
}: Partial<RechartsPrimitive.TooltipContentProps<any, any>> &
React.ComponentProps<"div"> & {
hideLabel?: boolean;
hideIndicator?: boolean;
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
}) {
const { config } = useChart();
const tooltipLabel = React.useMemo(() => {
@ -248,20 +320,20 @@ function ChartTooltipContent({
);
}
const ChartLegend = RechartsPrimitive.Legend;
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = "bottom",
nameKey,
}: React.ComponentProps<"div"> &
Pick<RechartsPrimitive.DefaultLegendContentProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean;
nameKey?: string;
}) {
const { config } = useChart();
const ChartLegend = RechartsPrimitive.Legend;
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = "bottom",
nameKey,
}: React.ComponentProps<"div"> &
Pick<RechartsPrimitive.DefaultLegendContentProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean;
nameKey?: string;
}) {
const { config } = useChart();
if (!payload?.length) {
return null;

View file

@ -105,7 +105,7 @@ export function TrainingSection() {
<div className="relative ">
<ChartContainer
config={chartConfig}
className="min-h-[180px] w-full relative right-8 w-full blur "
className="h-[180px] w-full relative right-8 blur"
>
<LineChart data={placeholderData} accessibilityLayer={true}>
<CartesianGrid vertical={false} strokeDasharray="3 3" />