Skip to content

fix: use latest reuseMaps value in map instance cleanup - #1042

Open
zigzagdev wants to merge 2 commits into
visgl:mainfrom
zigzagdev:fix-reuse-maps-stale-closure
Open

fix: use latest reuseMaps value in map instance cleanup#1042
zigzagdev wants to merge 2 commits into
visgl:mainfrom
zigzagdev:fix-reuse-maps-stale-closure

Conversation

@zigzagdev

@zigzagdev zigzagdev commented Aug 3, 2026

Copy link
Copy Markdown

Summary

Fixes a stale-closure bug in useMapInstance where toggling the reuseMaps prop right before unmounting a <Map> would silently do the wrong thing: an instance meant to be discarded could get cached indefinitely (leak), or an instance meant to be cached could have its listeners cleared instead.

What I have done

  • Added a reuseMapsRef in src/components/map/use-map-instance.ts, kept current via its own useEffect, and read it (instead of the closed-over reuseMaps prop) in both the map-creation and cleanup logic of the main effect.
  • Removed the now-stale eslint-disable-next-line react-hooks/set-state-in-effect comment that became unused once the new effect was added above it.
  • Added a regression test in src/components/__tests__/map.test.tsx that mounts with reuseMaps={true}, rerenders with reuseMaps={false} (keeping mapId/renderingType/colorScheme unchanged, since the effect intentionally doesn't rerun on reuseMaps alone), then unmounts and asserts clearInstanceListeners was called instead of the instance being pushed onto the internal cache stack.

Motivation

useMapInstance's map-creation effect intentionally excludes reuseMaps from its dependency array — toggling that prop alone shouldn't force the map to be torn down and recreated.
However, the effect's cleanup closure was reading reuseMaps directly, so it kept using whatever value was captured the last time the effect actually ran, not the current prop.
Concretely: mount with reuseMaps={true}, later switch to reuseMaps={false}, then unmount — the cleanup still saw the stale true and pushed the map instance onto the module-level CachedMapStack, leaking it indefinitely instead of clearing its listeners as intended.

Test Plans

  • Add a regression test asserting that unmounting after reuseMaps is toggled off (with the other cache-key props unchanged) calls clearInstanceListeners rather than caching the instance.
  • Confirm the new test fails against the pre-fix code and passes against the fix.
  • Run the full map.test.tsx suite and the full jest suite to check for regressions.
  • Run eslint and tsc --noEmit on the changed files.

Test Results

screen_shot 2026-08-03 20 43 06

Note: verification was run locally inside a node:22-bullseye Docker container (npm install + jest/eslint/tsc) — this Docker setup is local only and was not pushed to the repository.

reuseMaps was read directly from the effect's closure, but the effect
intentionally excludes it from its dependency array (toggling it alone
shouldn't recreate the map). As a result, if reuseMaps changed between
mount and unmount, the cleanup used the stale value captured at mount,
either leaking an instance onto the static cache stack or discarding
one that should have been cached. Track it in a ref that's kept current
via its own effect, and read that ref in both the create and cleanup
paths instead.
Adds a regression test for the stale-closure bug fixed in the previous
commit: mounts with reuseMaps enabled, rerenders with it disabled
(without changing mapId/renderingType/colorScheme, so the map-creation
effect intentionally doesn't rerun), then asserts that unmounting
clears the instance's listeners instead of pushing it onto the cache
stack.
@zigzagdev
zigzagdev marked this pull request as draft August 3, 2026 11:44
@zigzagdev zigzagdev changed the title Fix reuse maps stale closure fix: use latest reuseMaps value in map instance cleanup Aug 3, 2026
@zigzagdev
zigzagdev marked this pull request as ready for review August 3, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant