Skip to content

useMapstedMap() — React custom hook

This recipe provides a reusable useMapstedMap() hook that encapsulates the init / destroy lifecycle and exposes the MapInstance handle to your components.

For the full tutorial covering Next.js App Router, StrictMode notes, and the 'use client' directive, see Embed with React and Next.js.

The hook

tsx
// src/hooks/useMapstedMap.ts
import { useEffect, useRef, useState } from 'react';
import { init } from '@mapsted/maps-js-api';
import type { MapInstance } from '@mapsted/maps-js-api';

interface UseMapstedMapOptions {
  propertyId: number;
  mapsDomain?: string;
  onReady?: () => void;
}

interface UseMapstedMapResult {
  containerRef: React.RefObject<HTMLDivElement | null>;
  instance: MapInstance | null;
  ready: boolean;
}

export function useMapstedMap({
  propertyId,
  mapsDomain = 'https://maps.mapsted.com',
  onReady,
}: UseMapstedMapOptions): UseMapstedMapResult {
  const containerRef = useRef<HTMLDivElement>(null);
  const instanceRef = useRef<MapInstance | null>(null);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    if (!containerRef.current) return;

    let cancelled = false;

    init({
      element: containerRef.current,
      propertyId,
      mapsDomain,
      onload: () => {
        if (!cancelled) {
          setReady(true);
          onReady?.();
        }
      },
    }).then((instance) => {
      if (cancelled) {
        instance.destroy();
        return;
      }
      instanceRef.current = instance;
    });

    return () => {
      cancelled = true;
      setReady(false);
      instanceRef.current?.destroy();
      instanceRef.current = null;
    };
    // onReady intentionally excluded — consumers should stabilize the
    // reference with useCallback if they need it in deps.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [propertyId, mapsDomain]);

  return {
    containerRef,
    instance: instanceRef.current,
    ready,
  };
}

Usage

tsx
// src/components/MapView.tsx
import { useMapstedMap } from '../hooks/useMapstedMap';

export function MapView() {
  const { containerRef, instance, ready } = useMapstedMap({
    propertyId: 1643,
    onReady: () => console.log('Map is ready'),
  });

  function handleNavigate() {
    if (!ready || !instance) return;
    // Use any Maps JS API method — import from '@mapsted/maps-js-api'
    import('@mapsted/maps-js-api').then(({ navigateToFloorById }) => {
      navigateToFloorById(42);
    });
  }

  return (
    <>
      <div ref={containerRef} style={{ width: '100%', height: '100vh' }} />
      {ready && (
        <button onClick={handleNavigate}>Go to floor</button>
      )}
    </>
  );
}

Notes

  • StrictMode safety: The cancelled flag prevents the onload callback and instanceRef assignment from running after the cleanup has already fired, which is the pattern React 18 StrictMode exercises in development.
  • Instance stability: instanceRef.current is a mutable ref, not state. The component does not re-render when instance changes. If you need re-renders on instance availability, replace useRef with useState for the instance.
  • onReady stability: Pass a stable function reference (e.g. wrapped in useCallback) if you include onReady as a dependency elsewhere.