Skip to content

Commit b143533

Browse files
committed
fix: use static size for SSR
1 parent 6cad580 commit b143533

File tree

1 file changed

+66
-17
lines changed

1 file changed

+66
-17
lines changed

src/ReactQR.tsx

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {
1+
import React, {
22
forwardRef,
3-
useEffect,
3+
useLayoutEffect,
44
useMemo,
55
useRef,
66
useState,
@@ -20,6 +20,47 @@ const ERROR_CORRECTION_LEVEL_MAP: Record<
2020
H: qrcodegen.QrCode.Ecc.HIGH,
2121
} as const;
2222

23+
type SizedSvgProps = {
24+
width?: number | string;
25+
height?: number | string;
26+
viewBox?: string;
27+
};
28+
29+
function isSizedElement(
30+
node: React.ReactNode
31+
): node is React.ReactElement<SizedSvgProps> {
32+
return React.isValidElement(node);
33+
}
34+
35+
function parseNumber(val: unknown): number | null {
36+
if (typeof val === "number") return val;
37+
if (typeof val === "string") {
38+
const n = parseFloat(val);
39+
return Number.isFinite(n) ? n : null;
40+
}
41+
return null;
42+
}
43+
44+
function getIntrinsicSize(
45+
node: React.ReactNode
46+
): { w: number; h: number } | null {
47+
if (!isSizedElement(node)) return null;
48+
49+
const wAttr = parseNumber(node.props.width);
50+
const hAttr = parseNumber(node.props.height);
51+
if (wAttr && hAttr) return { w: wAttr, h: hAttr };
52+
53+
const vb = node.props.viewBox;
54+
if (typeof vb === "string") {
55+
const nums = vb.trim().split(/\s+/).map(parseFloat);
56+
if (nums.length === 4 && nums.every(Number.isFinite)) {
57+
const [, , w, h] = nums;
58+
return { w, h };
59+
}
60+
}
61+
return null;
62+
}
63+
2364
export interface ReactQRProps extends PropsWithChildren {
2465
value: string;
2566
size?: number;
@@ -36,8 +77,8 @@ export const ReactQR = forwardRef<SVGSVGElement, ReactQRProps>(
3677
size = 128,
3778
errorCorrectionLevel = "L",
3879
margin = 4,
39-
foregroundColor = "#000000",
40-
backgroundColor = "#ffffff",
80+
foregroundColor = "#000",
81+
backgroundColor = "#fff",
4182
children,
4283
},
4384
ref
@@ -57,20 +98,28 @@ export const ReactQR = forwardRef<SVGSVGElement, ReactQRProps>(
5798
}, [value, errorCorrectionLevel]);
5899

59100
const qrSize = qr?.size ?? 0;
60-
const cellSize = qrSize > 0 ? (size - margin * 2) / qrSize : 0;
101+
const cellSize = qrSize ? (size - margin * 2) / qrSize : 0;
61102

62-
const [{ w, h }, setOverlay] = useState({ w: 0, h: 0 });
103+
const staticSize = useMemo(() => getIntrinsicSize(children), [children]);
63104

64-
useEffect(() => {
65-
if (children && childrenSvgRef.current) {
66-
const { width, height } = childrenSvgRef.current.getBBox();
67-
setOverlay({ w: width, h: height });
68-
}
69-
}, [children]);
105+
const [{ w, h }, setOverlay] = useState(staticSize ?? { w: 0, h: 0 });
106+
107+
useLayoutEffect(() => {
108+
if (!children || staticSize || !childrenSvgRef.current) return;
109+
const { width, height } = childrenSvgRef.current.getBBox();
110+
setOverlay({ w: width, h: height });
111+
}, [children, staticSize]);
70112

71113
const mask = useMemo(() => {
72-
if (!children || !cellSize) {
73-
return { sx: -1, sy: -1, ex: -1, ey: -1, fx: 0, fy: 0 };
114+
if (!children || !cellSize || !w || !h) {
115+
return {
116+
sx: -1,
117+
sy: -1,
118+
ex: -1,
119+
ey: -1,
120+
fx: margin,
121+
fy: margin,
122+
};
74123
}
75124

76125
const startXf = (qrSize - w / cellSize) / 2;
@@ -116,24 +165,24 @@ export const ReactQR = forwardRef<SVGSVGElement, ReactQRProps>(
116165
height={size}
117166
viewBox={`0 0 ${size} ${size}`}
118167
xmlns="http://www.w3.org/2000/svg"
119-
xmlnsXlink="http://www.w3.org/1999/xlink"
120168
preserveAspectRatio="xMidYMid meet"
121169
>
122170
<path d={`M0,0h${size}v${size}h-${size}z`} fill={backgroundColor} />
123171
<path d={qrPath} fill={foregroundColor} shapeRendering="crispEdges" />
124-
{children ? (
172+
{children && (
125173
<svg
126174
ref={childrenSvgRef}
127175
x={mask.fx}
128176
y={mask.fy}
129177
width={w || 1}
130178
height={h || 1}
179+
style={{ width: w || 1, height: h || 1 }}
131180
viewBox={`0 0 ${w} ${h}`}
132181
pointerEvents="none"
133182
>
134183
{children}
135184
</svg>
136-
) : null}
185+
)}
137186
</svg>
138187
);
139188
}

0 commit comments

Comments
 (0)