diff --git a/src/components/QRCode.test.tsx b/src/components/QRCode.test.tsx
index feb3ec4..3d0056f 100644
--- a/src/components/QRCode.test.tsx
+++ b/src/components/QRCode.test.tsx
@@ -77,6 +77,54 @@ describe("QRCode", () => {
expect(document.querySelector("canvas")).toBeInTheDocument();
});
+ describe("size validation", () => {
+ it("warns and skips drawing when size is 0", () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const getContext = vi.spyOn(HTMLCanvasElement.prototype, "getContext");
+
+ render();
+
+ expect(warn).toHaveBeenCalledWith("[QRCode] size must be > 0");
+ expect(getContext).not.toHaveBeenCalled();
+
+ warn.mockRestore();
+ getContext.mockRestore();
+ });
+
+ it("warns and skips drawing when size is negative", () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const getContext = vi.spyOn(HTMLCanvasElement.prototype, "getContext");
+
+ render();
+
+ expect(warn).toHaveBeenCalledWith("[QRCode] size must be > 0");
+ expect(getContext).not.toHaveBeenCalled();
+
+ warn.mockRestore();
+ getContext.mockRestore();
+ });
+
+ it("does not leave a loading spinner running for an invalid size", () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+
+ const { container } = render();
+
+ expect(container.querySelector(".animate-spin")).not.toBeInTheDocument();
+
+ warn.mockRestore();
+ });
+
+ it("does not warn for a valid size", () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+
+ render();
+
+ expect(warn).not.toHaveBeenCalledWith("[QRCode] size must be > 0");
+
+ warn.mockRestore();
+ });
+ });
+
it("accepts a className on the outer wrapper", () => {
const { container } = render();
const wrapper = container.firstElementChild;
diff --git a/src/components/QRCode.tsx b/src/components/QRCode.tsx
index 87f1d82..7156f01 100644
--- a/src/components/QRCode.tsx
+++ b/src/components/QRCode.tsx
@@ -46,6 +46,11 @@ export function QRCode({
const [isLoading, setIsLoading] = useState(true);
const [prevProps, setPrevProps] = useState({ value, size, canvasBackground, canvasForeground });
+ // A zero or negative size gives the canvas invalid dimensions and renders as
+ // a broken box. Derived rather than stored so the spinner doesn't sit
+ // running while waiting on a draw that will never be attempted.
+ const hasInvalidSize = !size || size <= 0;
+
if (
prevProps.value !== value ||
prevProps.size !== size ||
@@ -59,6 +64,11 @@ export function QRCode({
useEffect(() => {
if (renderError || !value) return;
+ if (hasInvalidSize) {
+ console.warn("[QRCode] size must be > 0");
+ return;
+ }
+
const canvas = canvasRef.current;
if (!canvas) return;
@@ -100,7 +110,7 @@ export function QRCode({
return () => {
active = false;
};
- }, [value, size, canvasBackground, canvasForeground, renderError]);
+ }, [value, size, canvasBackground, canvasForeground, renderError, hasInvalidSize]);
return (
@@ -122,7 +132,7 @@ export function QRCode({
) : (
<>
- {isLoading && (
+ {isLoading && !hasInvalidSize && (