Skip to content

Commit 1c9111b

Browse files
committed
Optimize SVG loading and callback handling
Improves SVG component by preventing unnecessary reloads when the source and Two instance are unchanged, and ensures latest onLoad/onError callbacks are used via refs. Also updates Playground to use the correct RefGroup type for SVG onLoad callback.
1 parent 1dc176e commit 1c9111b

File tree

2 files changed

+34
-6
lines changed

2 files changed

+34
-6
lines changed

lib/SVG.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@ export const SVG = React.forwardRef<Instance, ComponentProps>(
5555
} = useTwo();
5656
const svg = useMemo(() => new Two.Group(), []);
5757
const ref = useRef<Instance | null>(null);
58+
const onLoadRef = useRef(onLoad);
59+
const onErrorRef = useRef(onError);
60+
const lastLoadedSource = useRef<{
61+
two: Two | null;
62+
key: string | null;
63+
}>({ two: null, key: null });
64+
65+
useEffect(() => {
66+
onLoadRef.current = onLoad;
67+
}, [onLoad]);
68+
69+
useEffect(() => {
70+
onErrorRef.current = onError;
71+
}, [onError]);
5872

5973
// Extract event handlers from props
6074
const { eventHandlers, shapeProps } = useMemo(() => {
@@ -113,7 +127,16 @@ export const SVG = React.forwardRef<Instance, ComponentProps>(
113127
const source = src || content;
114128
if (!source) return;
115129

130+
const currentKey = source;
131+
const last = lastLoadedSource.current;
132+
133+
// Skip reload if the source and Two instance are unchanged
134+
if (last.two === two && last.key === currentKey) {
135+
return;
136+
}
137+
116138
let mounted = true;
139+
lastLoadedSource.current = { two, key: currentKey };
117140

118141
try {
119142
// two.load() returns a Group immediately (empty initially)
@@ -125,11 +148,12 @@ export const SVG = React.forwardRef<Instance, ComponentProps>(
125148
ref.current?.add(loadedGroup.children);
126149

127150
// Invoke user callback if provided
128-
if (onLoad) {
151+
const handleLoad = onLoadRef.current;
152+
if (handleLoad) {
129153
try {
130154
// Wait until next frame once Two.js has computed
131155
// all necessary rendering / bounding box updates
132-
requestAnimationFrame(() => onLoad(ref.current!, svg));
156+
requestAnimationFrame(() => handleLoad(ref.current!, svg));
133157
} catch (err) {
134158
console.error(
135159
'[react-two.js] Error in SVG onLoad callback:',
@@ -145,9 +169,10 @@ export const SVG = React.forwardRef<Instance, ComponentProps>(
145169
const error =
146170
err instanceof Error ? err : new Error('Failed to load SVG');
147171

148-
if (onError) {
172+
const handleError = onErrorRef.current;
173+
if (handleError) {
149174
try {
150-
onError(error);
175+
handleError(error);
151176
} catch (callbackErr) {
152177
console.error(
153178
'[react-two.js] Error in SVG onError callback:',
@@ -163,10 +188,12 @@ export const SVG = React.forwardRef<Instance, ComponentProps>(
163188
// Note: Two.js XHR requests cannot be cancelled
164189
// We track mounted state to prevent setState on unmounted component
165190
mounted = false;
191+
// Reset last loaded key so the same source can be reloaded after cleanup
192+
lastLoadedSource.current = { two: null, key: null };
166193
// Remove previously added children
167194
ref.current?.remove(ref.current.children);
168195
};
169-
}, [two, src, content, onLoad, onError]);
196+
}, [two, src, content]);
170197

171198
// Update position and properties
172199
useEffect(() => {

src/Playground.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
Text,
3737
SVG,
3838
RefSVG,
39+
RefGroup,
3940
} from '../lib/main';
4041
import { useRef, useState } from 'react';
4142
import { useFrame } from '../lib/Context';
@@ -194,7 +195,7 @@ function Scene() {
194195
</svg>
195196
`}
196197
scale={0.6}
197-
onLoad={(svg) => {
198+
onLoad={(svg: RefGroup) => {
198199
svg.center();
199200
}}
200201
/>

0 commit comments

Comments
 (0)