Create the render worker lazily so StrictMode remounts do not leave it terminated
1 changed file+12−3
src/App.tsxmodified+12−3View file
@@ -23,16 +23,25 @@ export default function App() {
2323 const [rendering, setRendering] = useState(false)
2424 const [error, setError] = useState<string | null>(null)
2525 const [playing, setPlaying] = useState<string | null>(null)
26- const renderer = useMemo(() => new Renderer(), [])
26+ const rendererRef = useRef<Renderer | null>(null)
2727 const resultSource = useRef<Source | null>(null)
2828
29- useEffect(() => () => renderer.terminate(), [renderer])
29+ // The worker is created on demand and terminated on unmount. React StrictMode unmounts and
30+ // remounts in development, so a terminated worker must be replaced rather than kept.
31+ useEffect(
32+ () => () => {
33+ rendererRef.current?.terminate()
34+ rendererRef.current = null
35+ },
36+ [],
37+ )
3038
3139 // Re-render B whenever the source, the window, or the map changes (debounced for slider drags).
3240 useEffect(() => {
3341 if (!source) return
3442 setRendering(true)
3543 const timer = setTimeout(() => {
44+ const renderer = rendererRef.current ?? (rendererRef.current = new Renderer())
3645 renderer
3746 .request({ samples: source.samples, fs: source.fs, sigmaMs, map, fmax, maxCols: MAX_COLS, maxRows: MAX_ROWS, curvePoints: CURVE_POINTS })
3847 .then((r) => {
@@ -48,7 +57,7 @@ export default function App() {
4857 })
4958 }, RENDER_DEBOUNCE_MS)
5059 return () => clearTimeout(timer)
51- }, [source, sigmaMs, map, fmax, renderer])
60+ }, [source, sigmaMs, map, fmax])
5261
5362 const play = useCallback(
5463 async (label: string, which: ('A' | 'B')[]) => {