Skip to main content

Hooks System

React HERE Maps is built using a modern hook-based architecture that provides clean, composable functionality.

Core Hooks

useMapContext

Access the map context from any child component:
import { useMapContext } from '@rodrito/react-here-maps';

function MyComponent() {
  const { map, platform, ui, behavior } = useMapContext();

  // Use map objects...

  return null;
}
See Map Context for detailed usage.

useCreateMap

Internal hook used by HereMap to initialize the map instance. Not typically used directly, but understanding it helps with advanced customization.
const { map, mapRef, platform, ui, behavior } = useCreateMap({
  apikey: 'YOUR_API_KEY',
  options: {
    center: { lat: 0, lng: 0 },
    zoom: 10,
  },
  mapStyle: 'vector.normal.map',
});

useMapResize

Automatically handles map resize events. Used internally by HereMap:
import { useMapResize } from '@rodrito/react-here-maps';

const mapRef = useRef<H.Map | null>(null);
useMapResize(mapRef);

Component-Specific Hooks

useMarker

Powers the Marker component. Creates and manages markers:
import { useMarker } from '@rodrito/react-here-maps';

function CustomMarker({ position, label }: { position: { lat: number; lng: number }; label: string }) {
  useMarker({ position, label });
  return null;
}

useMarkerDragBehavior

Handles marker drag functionality:
const dragHandlers = useMarkerDragBehavior(map.current, behavior.current);

// Apply to marker
markerRef.current?.draggable = true;

usePolyline

Creates and manages polylines:
import { usePolyline } from '@rodrito/react-here-maps';

function CustomPolyline({ points }: { points: Array<{ lat: number; lng: number }> }) {
  usePolyline({ points });
  return null;
}

usePolygon

Creates and manages polygons:
import { usePolygon } from '@rodrito/react-here-maps';

function CustomPolygon({ points }: { points: Array<{ lat: number; lng: number }> }) {
  usePolygon({ points });
  return null;
}

Custom Hook Patterns

Creating a Map Bounds Hook

import { useMapContext } from '@rodrito/react-here-maps';
import { useState, useEffect } from 'react';

function useMapBounds() {
  const { map } = useMapContext();
  const [bounds, setBounds] = useState<H.geo.Rect | null>(null);

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

    const updateBounds = () => {
      setBounds(map.current!.getViewBounds());
    };

    // Update on map move
    map.current.addEventListener('mapviewchangeend', updateBounds);
    updateBounds(); // Initial bounds

    return () => {
      map.current?.removeEventListener('mapviewchangeend', updateBounds);
    };
  }, [map]);

  return bounds;
}

// Usage
function BoundsDisplay() {
  const bounds = useMapBounds();

  return (
    <div>
      {bounds && (
        <>
          <p>Top: {bounds.getTop().toFixed(4)}</p>
          <p>Bottom: {bounds.getBottom().toFixed(4)}</p>
          <p>Left: {bounds.getLeft().toFixed(4)}</p>
          <p>Right: {bounds.getRight().toFixed(4)}</p>
        </>
      )}
    </div>
  );
}

Geolocation Hook

import { useMapContext } from '@rodrito/react-here-maps';
import { useState, useEffect } from 'react';

function useGeolocation() {
  const { map } = useMapContext();
  const [position, setPosition] = useState<{ lat: number; lng: number } | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!navigator.geolocation) {
      setError('Geolocation not supported');
      return;
    }

    const success = (pos: GeolocationPosition) => {
      const coords = {
        lat: pos.coords.latitude,
        lng: pos.coords.longitude,
      };
      setPosition(coords);

      // Center map on user location
      if (map.current) {
        map.current.setCenter(coords);
        map.current.setZoom(14);
      }
    };

    const failed = (err: GeolocationPositionError) => {
      setError(err.message);
    };

    navigator.geolocation.getCurrentPosition(success, failed);
  }, [map]);

  return { position, error };
}

// Usage
function UserLocation() {
  const { position, error } = useGeolocation();

  if (error) return <div>Error: {error}</div>;
  if (!position) return <div>Getting location...</div>;

  return (
    <Marker
      position={position}
      label="You are here"
      icon="/user-location.png"
    />
  );
}

Map Events Hook

import { useMapContext } from '@rodrito/react-here-maps';
import { useEffect } from 'react';

function useMapEvents(
  events: Record<string, (evt: H.mapevents.Event) => void>
) {
  const { map } = useMapContext();

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

    // Add all event listeners
    Object.entries(events).forEach(([event, handler]) => {
      map.current!.addEventListener(event, handler);
    });

    // Cleanup
    return () => {
      Object.entries(events).forEach(([event, handler]) => {
        map.current?.removeEventListener(event, handler);
      });
    };
  }, [map, events]);
}

// Usage
function MapWithEvents() {
  const [clickCount, setClickCount] = useState(0);

  useMapEvents({
    tap: (evt) => {
      setClickCount((c) => c + 1);
      console.log('Map clicked', evt);
    },
    mapviewchangeend: () => {
      console.log('View changed');
    },
  });

  return <div>Clicks: {clickCount}</div>;
}

Distance Measurement Hook

import { useMapContext } from '@rodrito/react-here-maps';
import { useState, useEffect } from 'react';

function useDistanceMeasurement() {
  const { map } = useMapContext();
  const [points, setPoints] = useState<Array<{ lat: number; lng: number }>>([]);
  const [totalDistance, setTotalDistance] = useState(0);

  const addPoint = (lat: number, lng: number) => {
    setPoints([...points, { lat, lng }]);
  };

  const clearPoints = () => {
    setPoints([]);
    setTotalDistance(0);
  };

  useEffect(() => {
    if (points.length < 2) {
      setTotalDistance(0);
      return;
    }

    let distance = 0;
    for (let i = 0; i < points.length - 1; i++) {
      const p1 = new H.geo.Point(points[i].lat, points[i].lng);
      const p2 = new H.geo.Point(points[i + 1].lat, points[i + 1].lng);
      distance += p1.distance(p2);
    }

    setTotalDistance(distance);
  }, [points]);

  return { points, totalDistance, addPoint, clearPoints };
}

// Usage
function DistanceMeasureTool() {
  const { points, totalDistance, addPoint, clearPoints } = useDistanceMeasurement();

  useMapEvents({
    tap: (evt) => {
      const coord = map.current!.screenToGeo(
        evt.currentPointer.viewportX,
        evt.currentPointer.viewportY
      );
      addPoint(coord.lat, coord.lng);
    },
  });

  return (
    <div>
      <p>Distance: {(totalDistance / 1000).toFixed(2)} km</p>
      <button onClick={clearPoints}>Clear</button>
      {points.length > 1 && <Polyline points={points} />}
      {points.map((point, i) => (
        <Marker key={i} position={point} label={`${i + 1}`} />
      ))}
    </div>
  );
}

Hook Best Practices

Build complex functionality by composing simple hooks:
function useAdvancedMap() {
  const context = useMapContext();
  const bounds = useMapBounds();
  const location = useGeolocation();

  return { context, bounds, location };
}
Use useMemo and useCallback for expensive computations:
const filteredMarkers = useMemo(() => {
  return markers.filter(m => bounds.contains(m.position));
}, [markers, bounds]);
Always clean up side effects:
useEffect(() => {
  const listener = () => { /* ... */ };
  map.current?.addEventListener('tap', listener);
  return () => map.current?.removeEventListener('tap', listener);
}, [map]);
Define proper TypeScript types for custom hooks:
interface UseMapBoundsReturn {
  bounds: H.geo.Rect | null;
  refresh: () => void;
}

function useMapBounds(): UseMapBoundsReturn {
  // Implementation
}

See Also