#38·mapcn

[Enhancement] Consider using `useSyncExternalStore` where applicable

Author: junwen-kCreated Jan 9, 2026Updated Jan 9, 2026

useSyncExternalStore is a powerful hook that allows you to connect external stores to React components in an idiomatic way. This would be particularly beneficial for synchronizing with the MapLibre GL map instance state, making the code more declarative and potentially solving tearing with React concurrent rendering.

Example Use Case

Current <CompassButton />: Imperative DOM manipulation with useEffect

typescript
function CompassButton({ onClick }: { onClick: () => void }) {
  const { isLoaded, map } = useMap();
  const compassRef = useRef<SVGSVGElement>(null);

  useEffect(() => {
    if (!isLoaded || !map || !compassRef.current) return;

    const compass = compassRef.current;

    const updateRotation = () => {
      const bearing = map.getBearing();
      const pitch = map.getPitch();
      //  Manipulating transform style using ref, not so idiomatic React.
      compass.style.transform = `rotateX(${pitch}deg) rotateZ(${-bearing}deg)`;
    };

    map.on("rotate", updateRotation);
    map.on("pitch", updateRotation);
    updateRotation();

    return () => {
      map.off("rotate", updateRotation);
      map.off("pitch", updateRotation);
    };
  }, [isLoaded, map]);

  return (
    <ControlButton onClick={onClick} label="Reset bearing to north">
      <svg
        ref={compassRef}
        viewBox="0 0 24 24"
        className="size-5 transition-transform duration-200"
        style={{ transformStyle: "preserve-3d" }}
      >
        <path d="M12 2L16 12H12V2Z" className="fill-red-500" />
        <path d="M12 2L8 12H12V2Z" className="fill-red-300" />
        <path d="M12 22L16 12H12V22Z" className="fill-muted-foreground/60" />
        <path d="M12 22L8 12H12V22Z" className="fill-muted-foreground/30" />
      </svg>
    </ControlButton>
  );
}

Proposed <CompassButton />: Declarative state synchronization with useSyncExternalStore

typescript
function CompassButton({ onClick }: { onClick: () => void }) {
  const { map } = useMap();

  const bearing = useSyncExternalStore(
    (onStoreChange) => {
      map?.on("pitch", onStoreChange);
      map?.on("rotate", onStoreChange);

      return () => {
        map?.off("pitch", onStoreChange);
        map?.off("rotate", onStoreChange);
      };
    },
    () => map?.getBearing() ?? 0,
    () => map?.getBearing() ?? 0 // Server snapshot, map will be undefined and it will fallback to 0.
  );

  const pitch = useSyncExternalStore(
    (onStoreChange) => {
      map?.on("pitch", onStoreChange);

      return () => {
        map?.off("pitch", onStoreChange);
      };
    },
    () => map?.getPitch() ?? 0,
    () => map?.getPitch() ?? 0 // Server snapshot, map will be undefined and it will fallback to 0.
  );

  return (
    <ControlButton onClick={onClick} label="Reset bearing to north">
      <svg
        viewBox="0 0 24 24"
        className="size-5 transition-transform duration-200 transform-3d"
        style={{
          // ✅ Idiomatic React, declarative style.
          transform: `rotateX(${pitch}deg) rotateZ(${-bearing}deg)`,
        }}
      >
        <path d="M12 2L16 12H12V2Z" className="fill-red-500" />
        <path d="M12 2L8 12H12V2Z" className="fill-red-300" />
        <path d="M12 22L16 12H12V22Z" className="fill-muted-foreground/60" />
        <path d="M12 22L8 12H12V22Z" className="fill-muted-foreground/30" />
      </svg>
    </ControlButton>
  );
}

[!IMPORTANT]
I did not wrap my functions in useCallback because I had React compiler enabled in my project which automatically memoized the functions for me.

Additional Context

The compass button is just one example where useSyncExternalStore could be leveraged for a more idiomatic React integration. I haven't scanned the entire codebase to identify all potential use cases, but this hook is particularly useful for:

  • Any component that needs to read values from the map instance and stay in sync with map events (bounds, center, zoom level, etc.)
  • Components that currently use useEffect to subscribe to map events and update local state

Since we're already supporting React 19, useSyncExternalStore is available without any additional dependencies. It's the recommended React pattern for integrating external stores, especially useful for preventing potential tearing issues in concurrent rendering scenarios.

Should you come across similar scenarios where components need to remain synchronized with external state, useSyncExternalStore might be worth considering! If you agree with this approach, I'd be happy to open a PR to enhance this. Thanks for the great work as always!